Thats enough playtime for today. The mp3tunes service can now download the xml list...
[amarok.git] / src / scriptmanager.cpp
blob7b63818504aafee6ab1b74f5c7a273b4d4cba27a
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 "statusbar.h"
35 #include <k3procio.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 {
67 void closeOpenFiles(int out, int in, int err) {
68 for(int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--)
69 if(i!=out && i!=in && i!=err)
70 close(i);
73 /**
74 * This constructor is needed so that the correct codec is used. K3ProcIO defaults
75 * to latin1, while the scanner uses UTF-8.
77 ProcIO::ProcIO() : K3ProcIO( QTextCodec::codecForName( "UTF-8" ) ) {}
79 QString
80 proxyForUrl(const QString& url)
82 KUrl kurl( url );
84 QString proxy;
86 if ( KProtocolManager::proxyForUrl( kurl ) !=
87 QString::fromLatin1( "DIRECT" ) ) {
88 KProtocolManager::slaveProtocol ( kurl, proxy );
91 return proxy;
94 QString
95 proxyForProtocol(const QString& protocol)
97 return KProtocolManager::proxyFor( protocol );
103 ////////////////////////////////////////////////////////////////////////////////
104 // class AmarokScriptNewStuff
105 ////////////////////////////////////////////////////////////////////////////////
108 * GHNS Customised Download implementation.
110 #if 0 //TODO: PORT to KNS2
111 class AmarokScriptNewStuff : public KNewStuff
113 public:
114 AmarokScriptNewStuff(const QString &type, QWidget *parentWidget=0)
115 : KNewStuff( type, parentWidget )
118 bool install( const QString& fileName )
120 return ScriptManager::instance()->slotInstallScript( fileName );
123 virtual bool createUploadFile( const QString& ) { return false; } //make compile on kde 3.5
125 #endif
127 ////////////////////////////////////////////////////////////////////////////////
128 // class ScriptManager
129 ////////////////////////////////////////////////////////////////////////////////
131 ScriptManager* ScriptManager::s_instance = 0;
134 ScriptManager::ScriptManager( QWidget *parent, const char *name )
135 : KDialog( parent )
136 , EngineObserver( EngineController::instance() )
137 , m_gui( new Ui::ScriptManagerBase() )
139 DEBUG_BLOCK
140 setObjectName( name );
141 setModal( false );
142 setButtons( Close );
143 setDefaultButton( Close );
144 showButtonSeparator( true );
147 s_instance = this;
149 kapp->setTopWidget( this );
150 setCaption( KDialog::makeStandardCaption( i18n( "Script Manager" ) ) );
152 // Gives the window a small title bar, and skips a taskbar entry
153 #ifdef Q_WS_X11
154 KWindowSystem::setType( winId(), NET::Utility );
155 KWindowSystem::setState( winId(), NET::SkipTaskbar );
156 #endif
158 QWidget* main = new QWidget( this );
159 m_gui->setupUi( main );
161 setMainWidget( main );
163 /// Category items
164 m_generalCategory = new QTreeWidgetItem( m_gui->treeWidget );
165 m_lyricsCategory = new QTreeWidgetItem( m_gui->treeWidget );
166 m_scoreCategory = new QTreeWidgetItem( m_gui->treeWidget );
167 m_transcodeCategory = new QTreeWidgetItem( m_gui->treeWidget );
168 m_contextCategory = new QTreeWidgetItem( m_gui->treeWidget );
170 m_generalCategory ->setText( 0, i18n( "General" ) );
171 m_lyricsCategory ->setText( 0, i18n( "Lyrics" ) );
172 m_scoreCategory ->setText( 0, i18n( "Score" ) );
173 m_transcodeCategory->setText( 0, i18n( "Transcoding" ) );
174 m_contextCategory ->setText( 0, i18n( "Context Browser" ) );
176 m_generalCategory ->setFlags( Qt::ItemIsEnabled );
177 m_lyricsCategory ->setFlags( Qt::ItemIsEnabled );
178 m_scoreCategory ->setFlags( Qt::ItemIsEnabled );
179 m_transcodeCategory->setFlags( Qt::ItemIsEnabled );
180 m_contextCategory ->setFlags( Qt::ItemIsEnabled );
182 m_generalCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
183 m_lyricsCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
184 m_scoreCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
185 m_transcodeCategory->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
186 m_contextCategory ->setIcon( 0, SmallIcon( Amarok::icon( "files" ) ) );
188 // Restore the open/closed state of the category items
189 KConfigGroup config = Amarok::config( "ScriptManager" );
190 m_generalCategory ->setExpanded( config.readEntry( "General category open", false ) );
191 m_lyricsCategory ->setExpanded( config.readEntry( "Lyrics category open", false ) );
192 m_scoreCategory ->setExpanded( config.readEntry( "Score category State", false ) );
193 m_transcodeCategory->setExpanded( config.readEntry( "Transcode category open", false ) );
194 m_contextCategory ->setExpanded( config.readEntry( "Context category open", false ) );
196 connect( m_gui->treeWidget, SIGNAL( currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* ) ), SLOT( slotCurrentChanged( QTreeWidgetItem* ) ) );
197 connect( m_gui->treeWidget, SIGNAL( itemDoubleClicked( QTreeWidgetItem*, int ) ), SLOT( slotRunScript() ) );
198 connect( m_gui->treeWidget, SIGNAL( customContextMenuRequested ( const QPoint& ) ), SLOT( slotShowContextMenu( const QPoint& ) ) );
200 connect( m_gui->installButton, SIGNAL( clicked() ), SLOT( slotInstallScript() ) );
201 connect( m_gui->retrieveButton, SIGNAL( clicked() ), SLOT( slotRetrieveScript() ) );
202 connect( m_gui->uninstallButton, SIGNAL( clicked() ), SLOT( slotUninstallScript() ) );
203 connect( m_gui->runButton, SIGNAL( clicked() ), SLOT( slotRunScript() ) );
204 connect( m_gui->stopButton, SIGNAL( clicked() ), SLOT( slotStopScript() ) );
205 connect( m_gui->configureButton, SIGNAL( clicked() ), SLOT( slotConfigureScript() ) );
206 connect( m_gui->aboutButton, SIGNAL( clicked() ), SLOT( slotAboutScript() ) );
208 m_gui->installButton ->setIcon( KIcon( Amarok::icon( "files" ) ) );
209 m_gui->retrieveButton ->setIcon( KIcon( Amarok::icon( "download" ) ) );
210 m_gui->uninstallButton->setIcon( KIcon( Amarok::icon( "remove" ) ) );
211 m_gui->runButton ->setIcon( KIcon( Amarok::icon( "play" ) ) );
212 m_gui->stopButton ->setIcon( KIcon( Amarok::icon( "stop" ) ) );
213 m_gui->configureButton->setIcon( KIcon( Amarok::icon( "configure" ) ) );
214 m_gui->aboutButton ->setIcon( KIcon( Amarok::icon( "info" ) ) );
216 QSize sz = sizeHint();
217 setMinimumSize( qMax( 350, sz.width() ), qMax( 250, sz.height() ) );
218 resize( sizeHint() );
220 //FIXME: contex tbrowser changes
221 // connect( this, SIGNAL(lyricsScriptChanged()), ContextBrowser::instance(), SLOT( lyricsScriptChanged() ) );
223 // Delay this call via eventloop, because it's a bit slow and would block
224 QTimer::singleShot( 0, this, SLOT( findScripts() ) );
228 ScriptManager::~ScriptManager()
230 DEBUG_BLOCK
232 QStringList runningScripts;
233 foreach( QString key, m_scripts.keys() ) {
234 if( m_scripts[key].process ) {
235 terminateProcess( &m_scripts[key].process );
236 runningScripts << key;
240 // Save config
241 KConfigGroup config = Amarok::config( "ScriptManager" );
242 config.writeEntry( "Running Scripts", runningScripts );
244 // Save the open/closed state of the category items
245 config.writeEntry( "General category open", m_generalCategory->isExpanded() );
246 config.writeEntry( "Lyrics category open", m_lyricsCategory->isExpanded() );
247 config.writeEntry( "Score category open", m_scoreCategory->isExpanded() );
248 config.writeEntry( "Transcode category open", m_transcodeCategory->isExpanded() );
249 config.writeEntry( "Context category open", m_contextCategory->isExpanded() );
250 s_instance = 0;
254 ////////////////////////////////////////////////////////////////////////////////
255 // public
256 ////////////////////////////////////////////////////////////////////////////////
258 bool
259 ScriptManager::runScript( const QString& name, bool silent )
261 if( !m_scripts.contains( name ) )
262 return false;
264 m_gui->treeWidget->setCurrentItem( m_scripts[name].li );
265 return slotRunScript( silent );
269 bool
270 ScriptManager::stopScript( const QString& name )
272 if( !m_scripts.contains( name ) )
273 return false;
275 m_gui->treeWidget->setCurrentItem( m_scripts[name].li );
276 slotStopScript();
278 return true;
282 QStringList
283 ScriptManager::listRunningScripts()
285 QStringList runningScripts;
286 foreach( QString key, m_scripts.keys() )
287 if( m_scripts[key].process )
288 runningScripts << key;
290 return runningScripts;
294 void
295 ScriptManager::customMenuClicked( const QString& message )
297 notifyScripts( "customMenuClicked: " + message );
301 QString
302 ScriptManager::specForScript( const QString& name )
304 if( !m_scripts.contains( name ) )
305 return QString();
306 QFileInfo info( m_scripts[name].url.path() );
307 const QString specPath = info.path() + '/' + info.completeBaseName() + ".spec";
309 return specPath;
313 void
314 ScriptManager::notifyFetchLyrics( const QString& artist, const QString& title )
316 const QString args = QUrl::toPercentEncoding( artist ) + ' ' + QUrl::toPercentEncoding( title );
317 notifyScripts( "fetchLyrics " + args );
321 void
322 ScriptManager::notifyFetchLyricsByUrl( const QString& url )
324 notifyScripts( "fetchLyricsByUrl " + url );
328 void ScriptManager::notifyTranscode( const QString& srcUrl, const QString& filetype )
330 notifyScripts( "transcode " + srcUrl + ' ' + filetype );
334 void
335 ScriptManager::requestNewScore( const QString &url, double prevscore, int playcount, int length, float percentage, const QString &reason )
337 const QString script = ensureScoreScriptRunning();
338 if( script.isNull() )
340 Amarok::StatusBar::instance()->longMessage(
341 i18n( "No score scripts were found, or none of them worked. Automatic scoring will be disabled. Sorry." ),
342 KDE::StatusBar::Sorry );
343 return;
346 m_scripts[script].process->writeStdin(
347 QString( "requestNewScore %6 %1 %2 %3 %4 %5" )
348 .arg( prevscore )
349 .arg( playcount )
350 .arg( length )
351 .arg( percentage )
352 .arg( reason )
353 .arg( QString( QUrl::toPercentEncoding( url ) ) ) ); //last because it might have %s
356 ////////////////////////////////////////////////////////////////////////////////
357 // private slots
358 ////////////////////////////////////////////////////////////////////////////////
360 void
361 ScriptManager::findScripts() //SLOT
363 const QStringList allFiles = KGlobal::dirs()->findAllResources( "data", "amarok/scripts/*",KStandardDirs::Recursive );
365 // Add found scripts to treeWidget:
366 foreach( QString str, allFiles )
367 if( QFileInfo( str ).isExecutable() )
368 loadScript( str );
370 // Handle auto-run:
372 KConfigGroup config = Amarok::config( "ScriptManager" );
373 const QStringList runningScripts = config.readEntry( "Running Scripts", QStringList() );
375 foreach( QString str, runningScripts )
376 if( m_scripts.contains( str ) ) {
377 debug() << "Auto-running script: " << str;
378 m_gui->treeWidget->setCurrentItem( m_scripts[str].li );
379 slotRunScript();
382 //FIXME m_gui->treeWidget->setCurrentItem( m_gui->treeWidget->firstChild() );
383 slotCurrentChanged( m_gui->treeWidget->currentItem() );
387 void
388 ScriptManager::slotCurrentChanged( QTreeWidgetItem* item )
390 const bool isCategory = item == m_generalCategory ||
391 item == m_lyricsCategory ||
392 item == m_scoreCategory ||
393 item == m_transcodeCategory;
395 if( item && !isCategory ) {
396 const QString name = item->text( 0 );
397 m_gui->uninstallButton->setEnabled( true );
398 m_gui->runButton->setEnabled( !m_scripts[name].process );
399 m_gui->stopButton->setEnabled( m_scripts[name].process );
400 m_gui->configureButton->setEnabled( m_scripts[name].process );
401 m_gui->aboutButton->setEnabled( true );
403 else {
404 m_gui->uninstallButton->setEnabled( false );
405 m_gui->runButton->setEnabled( false );
406 m_gui->stopButton->setEnabled( false );
407 m_gui->configureButton->setEnabled( false );
408 m_gui->aboutButton->setEnabled( false );
413 bool
414 ScriptManager::slotInstallScript( const QString& path )
416 QString _path = path;
418 if( path.isNull() ) {
419 _path = KFileDialog::getOpenFileName( KUrl(),
420 "*.amarokscript.tar *.amarokscript.tar.bz2 *.amarokscript.tar.gz|"
421 + i18n( "Script Packages (*.amarokscript.tar, *.amarokscript.tar.bz2, *.amarokscript.tar.gz)" )
422 , this );
423 if( _path.isNull() ) return false;
426 KTar archive( _path );
427 if( !archive.open( QIODevice::ReadOnly ) ) {
428 KMessageBox::sorry( 0, i18n( "Could not read this package." ) );
429 return false;
432 QString destination = Amarok::saveLocation( "scripts/" );
433 const KArchiveDirectory* const archiveDir = archive.directory();
435 // Prevent installing a script that's already installed
436 const QString scriptFolder = destination + archiveDir->entries().first();
437 if( QFile::exists( scriptFolder ) ) {
438 KMessageBox::error( 0, i18n( "A script with the name '%1' is already installed. "
439 "Please uninstall it first.", archiveDir->entries().first() ) );
440 return false;
443 archiveDir->copyTo( destination );
444 m_installSuccess = false;
445 recurseInstall( archiveDir, destination );
447 if( m_installSuccess ) {
448 KMessageBox::information( 0, i18n( "Script successfully installed." ) );
449 return true;
451 else {
452 KMessageBox::sorry( 0, i18n( "<p>Script installation failed.</p>"
453 "<p>The package did not contain an executable file. "
454 "Please inform the package maintainer about this error.</p>" ) );
456 // Delete directory recursively
457 KIO::NetAccess::del( KUrl( scriptFolder ), 0 );
460 return false;
464 void
465 ScriptManager::recurseInstall( const KArchiveDirectory* archiveDir, const QString& destination )
467 const QStringList entries = archiveDir->entries();
469 foreach( QString entry, entries ) {
470 const KArchiveEntry* const archEntry = archiveDir->entry( entry );
472 if( archEntry->isDirectory() ) {
473 const KArchiveDirectory* const dir = static_cast<const KArchiveDirectory*>( archEntry );
474 recurseInstall( dir, destination + entry + '/' );
476 else {
477 ::chmod( QFile::encodeName( destination + entry ), archEntry->permissions() );
479 if( QFileInfo( destination + entry ).isExecutable() ) {
480 loadScript( destination + entry );
481 m_installSuccess = true;
488 void
489 ScriptManager::slotRetrieveScript()
491 #if 0 //FIXME: PORT To KNS2
492 // Delete KNewStuff's configuration entries. These entries reflect which scripts
493 // are already installed. As we cannot yet keep them in sync after uninstalling
494 // scripts, we deactivate the check marks entirely.
495 Amarok::config()->deleteGroup( "KNewStuffStatus" );
497 // we need this because KNewStuffGeneric's install function isn't clever enough
498 AmarokScriptNewStuff *kns = new AmarokScriptNewStuff( "amarok/script", this );
499 KNS::Engine *engine = new KNS::Engine( kns, "amarok/script", this );
500 KNS::DownloadDialog *d = new KNS::DownloadDialog( engine, this );
501 d->setType( "amarok/script" );
502 // you have to do this by hand when providing your own Engine
503 KNS::ProviderLoader *p = new KNS::ProviderLoader( this );
504 QObject::connect( p, SIGNAL( providersLoaded(Provider::List*) ), d, SLOT( slotProviders (Provider::List *) ) );
505 p->load( "amarok/script", "http://amarok.kde.org/knewstuff/amarokscripts-providers.xml" );
507 d->exec();
508 #endif
512 void
513 ScriptManager::slotUninstallScript()
515 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
517 if( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to uninstall the script '%1'?", name ), i18n("Uninstall Script"), KGuiItem( i18n("Uninstall") ) ) == KMessageBox::Cancel )
518 return;
520 if( m_scripts.find( name ) == m_scripts.end() )
521 return;
523 const QString directory = m_scripts[name].url.directory();
525 // Delete directory recursively
526 const KUrl url = KUrl( directory );
527 if( !KIO::NetAccess::del( url, 0 ) ) {
528 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>" ) );
529 return;
532 QStringList keys;
534 // Find all scripts that were in the uninstalled folder
535 foreach( QString key, m_scripts.keys() )
536 if( m_scripts[key].url.directory() == directory )
537 keys << key;
539 // Terminate script processes, remove entries from script list
540 foreach( QString key, keys ) {
541 delete m_scripts[key].li;
542 terminateProcess( &m_scripts[key].process );
543 m_scripts.remove( key );
548 bool
549 ScriptManager::slotRunScript( bool silent )
551 if( !m_gui->runButton->isEnabled() ) return false;
553 QTreeWidgetItem* const li = m_gui->treeWidget->currentItem();
554 const QString name = li->text( 0 );
556 if( m_scripts[name].type == "lyrics" && lyricsScriptRunning() != QString::null ) {
557 if( !silent )
558 KMessageBox::sorry( 0, i18n( "Another lyrics script is already running. "
559 "You may only run one lyrics script at a time." ) );
560 return false;
563 if( m_scripts[name].type == "transcode" && transcodeScriptRunning() != QString::null ) {
564 if( !silent )
565 KMessageBox::sorry( 0, i18n( "Another transcode script is already running. "
566 "You may only run one transcode script at a time." ) );
567 return false;
570 // Don't start a script twice
571 if( m_scripts[name].process ) return false;
573 Amarok::ProcIO* script = new Amarok::ProcIO();
574 script->setComm( static_cast<K3Process::Communication>( K3Process::All ) );
575 const KUrl url = m_scripts[name].url;
576 *script << url.path();
577 script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) );
579 connect( script, SIGNAL( receivedStderr( K3Process*, char*, int ) ), SLOT( slotReceivedStderr( K3Process*, char*, int ) ) );
580 connect( script, SIGNAL( receivedStdout( K3Process*, char*, int ) ), SLOT( slotReceivedStdout( K3Process*, char*, int ) ) );
581 connect( script, SIGNAL( processExited( K3Process* ) ), SLOT( scriptFinished( K3Process* ) ) );
583 if( script->start( K3Process::NotifyOnExit ) )
585 if( m_scripts[name].type == "score" && !scoreScriptRunning().isNull() )
587 stopScript( scoreScriptRunning() );
588 m_gui->treeWidget->setCurrentItem( li );
590 AmarokConfig::setLastScoreScript( name );
592 else
594 if( !silent )
595 KMessageBox::sorry( 0, i18n( "<p>Could not start the script <i>%1</i>.</p>"
596 "<p>Please make sure that the file has execute (+x) permissions.</p>", name ) );
597 delete script;
598 return false;
601 li->setIcon( 0, SmallIcon( Amarok::icon( "play" ) ) );
602 debug() << "Running script: " << url.path();
604 m_scripts[name].process = script;
605 slotCurrentChanged( m_gui->treeWidget->currentItem() );
606 if( m_scripts[name].type == "lyrics" )
607 emit lyricsScriptChanged();
609 return true;
613 void
614 ScriptManager::slotStopScript()
616 QTreeWidgetItem* const li = m_gui->treeWidget->currentItem();
617 const QString name = li->text( 0 );
619 // Just a sanity check
620 if( m_scripts.find( name ) == m_scripts.end() )
621 return;
623 terminateProcess( &m_scripts[name].process );
624 m_scripts[name].log = QString::null;
625 slotCurrentChanged( m_gui->treeWidget->currentItem() );
627 li->setIcon( 0, QPixmap() );
631 void
632 ScriptManager::slotConfigureScript()
634 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
635 if( !m_scripts[name].process ) return;
637 const KUrl url = m_scripts[name].url;
638 QDir::setCurrent( url.directory() );
640 m_scripts[name].process->writeStdin( QString("configure") );
644 void
645 ScriptManager::slotAboutScript()
647 const QString name = m_gui->treeWidget->currentItem()->text( 0 );
648 QFile readme( m_scripts[name].url.directory( KUrl::AppendTrailingSlash ) + "README" );
649 QFile license( m_scripts[name].url.directory( KUrl::AppendTrailingSlash) + "COPYING" );
651 if( !readme.open( QIODevice::ReadOnly ) ) {
652 KMessageBox::sorry( 0, i18n( "There is no information available for this script." ) );
653 return;
656 KAboutData aboutData( name.toLatin1(), 0, ki18n(name.toLatin1()), "1.0", ki18n(readme.readAll()) );
658 KAboutApplicationDialog* about = new KAboutApplicationDialog( &aboutData, this );
659 about->setButtons( KDialog::Ok );
660 about->setDefaultButton( KDialog::Ok );
662 kapp->setTopWidget( about );
663 about->setCaption( KDialog::makeStandardCaption( i18n( "About %1", name ) ) );
665 about->setInitialSize( QSize( 500, 350 ) );
666 about->show();
670 void
671 ScriptManager::slotShowContextMenu( const QPoint& pos )
673 QTreeWidgetItem* item = m_gui->treeWidget->itemAt( pos );
675 const bool isCategory = item == m_generalCategory ||
676 item == m_lyricsCategory ||
677 item == m_scoreCategory ||
678 item == m_transcodeCategory ||
679 item == m_contextCategory;
681 if( !item || isCategory ) return;
683 // Find the script entry in our map
684 QString key;
685 foreach( key, m_scripts.keys() )
686 if( m_scripts[key].li == item ) break;
688 enum { SHOW_LOG, EDIT };
689 KMenu menu;
690 menu.addTitle( i18n( "Debugging" ) );
691 QAction* logAction = menu.addAction( KIcon( Amarok::icon( "clock" ) ), i18n( "Show Output &Log" ) );
692 QAction* editAction = menu.addAction( KIcon( Amarok::icon( "edit" ) ), i18n( "&Edit" ) );
693 logAction->setData( SHOW_LOG );
694 editAction->setData( EDIT );
696 logAction->setEnabled( m_scripts[key].process != 0 );
698 QAction* choice = menu.exec( mapToGlobal( pos ) );
699 if( !choice ) return;
700 const int id = choice->data().toInt();
702 switch( id )
704 case EDIT:
705 KRun::runCommand( "kwrite " + m_scripts[key].url.path(), 0 );
706 break;
708 case SHOW_LOG:
709 QString line;
710 while( m_scripts[key].process->readln( line ) != -1 )
711 m_scripts[key].log += line;
713 KTextEdit* editor = new KTextEdit( m_scripts[key].log );
714 kapp->setTopWidget( editor );
715 editor->setWindowTitle( KDialog::makeStandardCaption( i18n( "Output Log for %1" ).arg( key ) ) );
716 editor->setReadOnly( true );
718 QFont font( "fixed" );
719 font.setFixedPitch( true );
720 font.setStyleHint( QFont::TypeWriter );
721 editor->setFont( font );
723 editor->resize( 500, 380 );
724 editor->show();
725 break;
730 /* This is just a workaround, some scripts crash for some people if stdout is not handled. */
731 void
732 ScriptManager::slotReceivedStdout( K3Process*, char* buf, int len )
734 debug() << QString::fromLatin1( buf, len );
738 void
739 ScriptManager::slotReceivedStderr( K3Process* process, char* buf, int len )
741 // Look up script entry in our map
742 ScriptMap::Iterator it;
743 ScriptMap::Iterator end( m_scripts.end() );
744 for( it = m_scripts.begin(); it != end; ++it )
745 if( it.value().process == process ) break;
747 const QString text = QString::fromLatin1( buf, len );
748 error() << it.key() << ":\n" << text;
750 if( it.value().log.length() > 20000 )
751 it.value().log = "==== LOG TRUNCATED HERE ====\n";
752 it.value().log += text;
756 void
757 ScriptManager::scriptFinished( K3Process* process ) //SLOT
759 // Look up script entry in our map
760 ScriptMap::Iterator it;
761 ScriptMap::Iterator end( m_scripts.end() );
762 for( it = m_scripts.begin(); it != end; ++it )
763 if( it.value().process == process ) break;
765 // Check if there was an error on exit
766 if( process->normalExit() && process->exitStatus() != 0 )
767 KMessageBox::detailedError( 0, i18n( "The script '%1' exited with error code: %2", it.key(), process->exitStatus() )
768 ,it.value().log );
770 // Destroy script process
771 delete it.value().process;
772 it.value().process = 0;
773 it.value().log.clear();
774 it.value().li->setIcon( 0, QPixmap() );
775 slotCurrentChanged( m_gui->treeWidget->currentItem() );
779 ////////////////////////////////////////////////////////////////////////////////
780 // private
781 ////////////////////////////////////////////////////////////////////////////////
783 QStringList
784 ScriptManager::scriptsOfType( const QString &type ) const
786 QStringList scripts;
787 foreach( QString key, m_scripts.keys() )
788 if( m_scripts[key].type == type )
789 scripts += key;
791 return scripts;
795 QString
796 ScriptManager::scriptRunningOfType( const QString &type ) const
798 foreach( QString key, m_scripts.keys() )
799 if( m_scripts[key].process && m_scripts[key].type == type )
800 return key;
802 return QString();
806 QString
807 ScriptManager::ensureScoreScriptRunning()
809 QString s = scoreScriptRunning();
810 if( !s.isNull() )
811 return s;
813 if( runScript( AmarokConfig::lastScoreScript(), true /*silent*/ ) )
814 return AmarokConfig::lastScoreScript();
816 const QString def = i18n( "Score" ) + ": " + "Default";
817 if( runScript( def, true ) )
818 return def;
820 const QStringList scripts = scoreScripts();
821 for( QStringList::const_iterator it = scripts.begin(), end = scripts.end(); it != end; ++it )
822 if( runScript( *it, true ) )
823 return *it;
825 return QString();
829 void
830 ScriptManager::terminateProcess( K3ProcIO** proc )
832 if( *proc ) {
833 (*proc)->kill(); // Sends SIGTERM
834 (*proc)->detach();
836 delete *proc;
837 *proc = 0;
842 void
843 ScriptManager::notifyScripts( const QString& message )
845 foreach( ScriptItem item, m_scripts ) {
846 K3ProcIO* const proc = item.process;
847 if( proc ) proc->writeStdin( message );
852 void
853 ScriptManager::loadScript( const QString& path )
855 if( !path.isEmpty() ) {
856 const KUrl url = KUrl( path );
857 QString name = url.fileName();
858 QString type = "generic";
860 // Read and parse .spec file, if exists
861 QFileInfo info( path );
862 QTreeWidgetItem* li = 0;
863 const QString specPath = info.path() + '/' + info.completeBaseName() + ".spec";
864 if( QFile::exists( specPath ) ) {
865 debug() << "Spec file found: " << specPath;
866 QSettings spec( specPath, QSettings::IniFormat );
867 if( spec.contains( "name" ) )
868 name = spec.value( "name" ).toString();
869 if( spec.contains( "type" ) ) {
870 type = spec.value( "type" ).toString();
871 if( type == "lyrics" ) {
872 li = new QTreeWidgetItem( m_lyricsCategory );
873 li->setText( 0, name );
875 if( type == "transcode" ) {
876 li = new QTreeWidgetItem( m_transcodeCategory );
877 li->setText( 0, name );
879 if( type == "score" ) {
880 li = new QTreeWidgetItem( m_scoreCategory );
881 li->setText( 0, name );
883 if( type == "context" ) {
884 li = new QTreeWidgetItem( m_contextCategory );
885 li->setText( 0, name );
890 if( !li ) {
891 li = new QTreeWidgetItem( m_generalCategory );
892 li->setText( 0, name );
895 li->setIcon( 0, QPixmap() );
897 ScriptItem item;
898 item.url = url;
899 item.type = type;
900 item.process = 0;
901 item.li = li;
903 m_scripts[name] = item;
904 debug() << "Loaded: " << name;
906 slotCurrentChanged( m_gui->treeWidget->currentItem() );
911 void
912 ScriptManager::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
914 switch( state )
916 case Engine::Empty:
917 notifyScripts( "engineStateChange: empty" );
918 break;
920 case Engine::Idle:
921 notifyScripts( "engineStateChange: idle" );
922 break;
924 case Engine::Paused:
925 notifyScripts( "engineStateChange: paused" );
926 break;
928 case Engine::Playing:
929 notifyScripts( "engineStateChange: playing" );
930 break;
935 void
936 ScriptManager::engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ )
938 notifyScripts( "trackChange" );
942 void
943 ScriptManager::engineVolumeChanged( int newVolume )
945 notifyScripts( "volumeChange: " + QString::number( newVolume ) );
949 #include "scriptmanager.moc"