/*************************************************************************** * Copyright (C) 2004-2006 by Mark Kretschmann * * 2005 by Seb Ruiz * * 2006 by Alexandre Oliveira * * 2006 by Martin Ellis * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #define DEBUG_PREFIX "ScriptManager" #include "amarok.h" #include "amarokconfig.h" #include "contextbrowser.h" #include "debug.h" #include "enginecontroller.h" #include "metabundle.h" #include "scriptmanager.h" #include "scriptmanagerbase.h" #include "statusbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // knewstuff script fetching #include // " #include // " #include // " namespace Amarok { void closeOpenFiles(int out, int in, int err) { for(int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; i--) if(i!=out && i!=in && i!=err) close(i); } /** * This constructor is needed so that the correct codec is used. KProcIO defaults * to latin1, while the scanner uses UTF-8. */ ProcIO::ProcIO() : KProcIO( TQTextCodec::codecForName( "UTF-8" ) ) {} TQString proxyForUrl(const TQString& url) { KURL kurl( url ); TQString proxy; if ( KProtocolManager::proxyForURL( kurl ) != TQString::fromLatin1( "DIRECT" ) ) { KProtocolManager::slaveProtocol ( kurl, proxy ); } return proxy; } TQString proxyForProtocol(const TQString& protocol) { return KProtocolManager::proxyFor( protocol ); } } //////////////////////////////////////////////////////////////////////////////// // class AmarokScriptNewStuff //////////////////////////////////////////////////////////////////////////////// /** * GHNS Customised Download implementation. */ class AmarokScriptNewStuff : public KNewStuff { public: AmarokScriptNewStuff(const TQString &type, TQWidget *parentWidget=0) : KNewStuff( type, parentWidget ) {} bool install( const TQString& fileName ) { return ScriptManager::instance()->slotInstallScript( fileName ); } virtual bool createUploadFile( const TQString& ) { return false; } //make compile on kde 3.5 }; //////////////////////////////////////////////////////////////////////////////// // class ScriptManager //////////////////////////////////////////////////////////////////////////////// ScriptManager* ScriptManager::s_instance = 0; ScriptManager::ScriptManager( TQWidget *parent, const char *name ) : KDialogBase( parent, name, false, TQString(), Close, Close, true ) , EngineObserver( EngineController::instance() ) , m_gui( new ScriptManagerBase( this ) ) { DEBUG_BLOCK s_instance = this; kapp->setTopWidget( this ); setCaption( kapp->makeStdCaption( i18n( "Script Manager" ) ) ); // Gives the window a small title bar, and skips a taskbar entry KWin::setType( winId(), NET::Utility ); KWin::setState( winId(), NET::SkipTaskbar ); setMainWidget( m_gui ); m_gui->listView->setRootIsDecorated( true ); m_gui->listView->setFullWidth( true ); m_gui->listView->setShowSortIndicator( true ); /// Category items m_generalCategory = new KListViewItem( m_gui->listView, i18n( "General" ) ); m_lyricsCategory = new KListViewItem( m_gui->listView, i18n( "Lyrics" ) ); m_scoreCategory = new KListViewItem( m_gui->listView, i18n( "Score" ) ); m_transcodeCategory = new KListViewItem( m_gui->listView, i18n( "Transcoding" ) ); m_generalCategory ->setSelectable( false ); m_lyricsCategory ->setSelectable( false ); m_scoreCategory ->setSelectable( false ); m_transcodeCategory->setSelectable( false ); m_generalCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); m_lyricsCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); m_scoreCategory ->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); m_transcodeCategory->setPixmap( 0, SmallIcon( Amarok::icon( "files" ) ) ); // Restore the open/closed state of the category items KConfig* const config = Amarok::config( "ScriptManager" ); m_generalCategory ->setOpen( config->readBoolEntry( "General category open" ) ); m_lyricsCategory ->setOpen( config->readBoolEntry( "Lyrics category open" ) ); m_scoreCategory ->setOpen( config->readBoolEntry( "Score category State" ) ); m_transcodeCategory->setOpen( config->readBoolEntry( "Transcode category open" ) ); connect( m_gui->listView, TQT_SIGNAL( currentChanged( TQListViewItem* ) ), TQT_SLOT( slotCurrentChanged( TQListViewItem* ) ) ); connect( m_gui->listView, TQT_SIGNAL( doubleClicked ( TQListViewItem*, const TQPoint&, int ) ), TQT_SLOT( slotRunScript() ) ); connect( m_gui->listView, TQT_SIGNAL( contextMenuRequested ( TQListViewItem*, const TQPoint&, int ) ), TQT_SLOT( slotShowContextMenu( TQListViewItem*, const TQPoint& ) ) ); connect( m_gui->installButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotInstallScript() ) ); connect( m_gui->retrieveButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotRetrieveScript() ) ); connect( m_gui->uninstallButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotUninstallScript() ) ); connect( m_gui->runButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotRunScript() ) ); connect( m_gui->stopButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotStopScript() ) ); connect( m_gui->configureButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotConfigureScript() ) ); connect( m_gui->aboutButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotAboutScript() ) ); m_gui->installButton ->setIconSet( SmallIconSet( Amarok::icon( "files" ) ) ); m_gui->retrieveButton ->setIconSet( SmallIconSet( Amarok::icon( "download" ) ) ); m_gui->uninstallButton->setIconSet( SmallIconSet( Amarok::icon( "remove" ) ) ); m_gui->runButton ->setIconSet( SmallIconSet( Amarok::icon( "play" ) ) ); m_gui->stopButton ->setIconSet( SmallIconSet( Amarok::icon( "stop" ) ) ); m_gui->configureButton->setIconSet( SmallIconSet( Amarok::icon( "configure" ) ) ); m_gui->aboutButton ->setIconSet( SmallIconSet( Amarok::icon( "info" ) ) ); TQSize sz = sizeHint(); setMinimumSize( kMax( 350, sz.width() ), kMax( 250, sz.height() ) ); resize( sizeHint() ); connect( this, TQT_SIGNAL(lyricsScriptChanged()), ContextBrowser::instance(), TQT_SLOT( lyricsScriptChanged() ) ); // Delay this call via eventloop, because it's a bit slow and would block TQTimer::singleShot( 0, this, TQT_SLOT( findScripts() ) ); } ScriptManager::~ScriptManager() { DEBUG_BLOCK TQStringList runningScripts; ScriptMap::Iterator it; ScriptMap::Iterator end( m_scripts.end() ); for( it = m_scripts.begin(); it != end; ++it ) { if( it.data().process ) { terminateProcess( &it.data().process ); runningScripts << it.key(); } } // Save config KConfig* const config = Amarok::config( "ScriptManager" ); config->writeEntry( "Running Scripts", runningScripts ); // Save the open/closed state of the category items config->writeEntry( "General category open", m_generalCategory->isOpen() ); config->writeEntry( "Lyrics category open", m_lyricsCategory->isOpen() ); config->writeEntry( "Score category open", m_scoreCategory->isOpen() ); config->writeEntry( "Transcode category open", m_transcodeCategory->isOpen() ); s_instance = 0; } //////////////////////////////////////////////////////////////////////////////// // public //////////////////////////////////////////////////////////////////////////////// bool ScriptManager::runScript( const TQString& name, bool silent ) { if( !m_scripts.contains( name ) ) return false; m_gui->listView->setCurrentItem( m_scripts[name].li ); return slotRunScript( silent ); } bool ScriptManager::stopScript( const TQString& name ) { if( !m_scripts.contains( name ) ) return false; m_gui->listView->setCurrentItem( m_scripts[name].li ); slotStopScript(); return true; } TQStringList ScriptManager::listRunningScripts() { TQStringList runningScripts; foreachType( ScriptMap, m_scripts ) if( it.data().process ) runningScripts << it.key(); return runningScripts; } void ScriptManager::customMenuClicked( const TQString& message ) { notifyScripts( "customMenuClicked: " + message ); } TQString ScriptManager::specForScript( const TQString& name ) { if( !m_scripts.contains( name ) ) return TQString(); TQFileInfo info( m_scripts[name].url.path() ); const TQString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec"; return specPath; } void ScriptManager::notifyFetchLyrics( const TQString& artist, const TQString& title ) { const TQString args = KURL::encode_string( artist ) + ' ' + KURL::encode_string( title ); notifyScripts( "fetchLyrics " + args ); } void ScriptManager::notifyFetchLyricsByUrl( const TQString& url ) { notifyScripts( "fetchLyricsByUrl " + url ); } void ScriptManager::notifyTranscode( const TQString& srcUrl, const TQString& filetype ) { notifyScripts( "transcode " + srcUrl + ' ' + filetype ); } void ScriptManager::notifyPlaylistChange( const TQString& change) { notifyScripts( "playlistChange: " + change ); } void ScriptManager::requestNewScore( const TQString &url, double prevscore, int playcount, int length, float percentage, const TQString &reason ) { const TQString script = ensureScoreScriptRunning(); if( script.isNull() ) { Amarok::StatusBar::instance()->longMessage( i18n( "No score scripts were found, or none of them worked. Automatic scoring will be disabled. Sorry." ), KDE::StatusBar::Sorry ); return; } m_scripts[script].process->writeStdin( TQString( "requestNewScore %6 %1 %2 %3 %4 %5" ) .arg( prevscore ) .arg( playcount ) .arg( length ) .arg( percentage ) .arg( reason ) .arg( KURL::encode_string( url ) ) ); //last because it might have %s } //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void ScriptManager::findScripts() //SLOT { const TQStringList allFiles = kapp->dirs()->findAllResources( "data", "amarok/scripts/*", true ); // Add found scripts to listview: { foreach( allFiles ) if( TQFileInfo( *it ).isExecutable() ) loadScript( *it ); } // Handle auto-run: KConfig* const config = Amarok::config( "ScriptManager" ); const TQStringList runningScripts = config->readListEntry( "Running Scripts" ); { foreach( runningScripts ) if( m_scripts.contains( *it ) ) { debug() << "Auto-running script: " << *it << endl; m_gui->listView->setCurrentItem( m_scripts[*it].li ); slotRunScript(); } } m_gui->listView->setCurrentItem( m_gui->listView->firstChild() ); slotCurrentChanged( m_gui->listView->currentItem() ); } void ScriptManager::slotCurrentChanged( TQListViewItem* item ) { const bool isCategory = item == m_generalCategory || item == m_lyricsCategory || item == m_scoreCategory || item == m_transcodeCategory; if( item && !isCategory ) { const TQString name = item->text( 0 ); m_gui->uninstallButton->setEnabled( true ); m_gui->runButton->setEnabled( !m_scripts[name].process ); m_gui->stopButton->setEnabled( m_scripts[name].process ); m_gui->configureButton->setEnabled( m_scripts[name].process ); m_gui->aboutButton->setEnabled( true ); } else { m_gui->uninstallButton->setEnabled( false ); m_gui->runButton->setEnabled( false ); m_gui->stopButton->setEnabled( false ); m_gui->configureButton->setEnabled( false ); m_gui->aboutButton->setEnabled( false ); } } bool ScriptManager::slotInstallScript( const TQString& path ) { TQString _path = path; if( path.isNull() ) { _path = KFileDialog::getOpenFileName( TQString(), "*.amarokscript.tar *.amarokscript.tar.bz2 *.amarokscript.tar.gz|" + i18n( "Script Packages (*.amarokscript.tar, *.amarokscript.tar.bz2, *.amarokscript.tar.gz)" ) , this , i18n( "Select Script Package" ) ); if( _path.isNull() ) return false; } KTar archive( _path ); if( !archive.open( IO_ReadOnly ) ) { KMessageBox::sorry( 0, i18n( "Could not read this package." ) ); return false; } TQString destination = Amarok::saveLocation( "scripts/" ); const KArchiveDirectory* const archiveDir = archive.directory(); // Prevent installing a script that's already installed const TQString scriptFolder = destination + archiveDir->entries().first(); if( TQFile::exists( scriptFolder ) ) { KMessageBox::error( 0, i18n( "A script with the name '%1' is already installed. " "Please uninstall it first." ).arg( archiveDir->entries().first() ) ); return false; } archiveDir->copyTo( destination ); m_installSuccess = false; recurseInstall( archiveDir, destination ); if( m_installSuccess ) { KMessageBox::information( 0, i18n( "Script successfully installed." ) ); return true; } else { KMessageBox::sorry( 0, i18n( "

Script installation failed.

" "

The package did not contain an executable file. " "Please inform the package maintainer about this error.

" ) ); // Delete directory recursively KIO::NetAccess::del( KURL::fromPathOrURL( scriptFolder ), 0 ); } return false; } void ScriptManager::recurseInstall( const KArchiveDirectory* archiveDir, const TQString& destination ) { const TQStringList entries = archiveDir->entries(); foreach( entries ) { const TQString entry = *it; const KArchiveEntry* const archEntry = archiveDir->entry( entry ); if( archEntry->isDirectory() ) { const KArchiveDirectory* const dir = static_cast( archEntry ); recurseInstall( dir, destination + entry + '/' ); } else { ::chmod( TQFile::encodeName( destination + entry ), archEntry->permissions() ); if( TQFileInfo( destination + entry ).isExecutable() ) { loadScript( destination + entry ); m_installSuccess = true; } } } } void ScriptManager::slotRetrieveScript() { // Delete KNewStuff's configuration entries. These entries reflect which scripts // are already installed. As we cannot yet keep them in sync after uninstalling // scripts, we deactivate the check marks entirely. Amarok::config()->deleteGroup( "KNewStuffStatus" ); // we need this because KNewStuffGeneric's install function isn't clever enough AmarokScriptNewStuff *kns = new AmarokScriptNewStuff( "amarok/script", this ); KNS::Engine *engine = new KNS::Engine( kns, "amarok/script", this ); KNS::DownloadDialog *d = new KNS::DownloadDialog( engine, this ); d->setType( "amarok/script" ); // you have to do this by hand when providing your own Engine KNS::ProviderLoader *p = new KNS::ProviderLoader( this ); TQObject::connect( p, TQT_SIGNAL( providersLoaded(Provider::List*) ), d, TQT_SLOT( slotProviders (Provider::List *) ) ); p->load( "amarok/script", "http://amarok.kde.org/knewstuff/amarokscripts-providers.xml" ); d->exec(); } void ScriptManager::slotUninstallScript() { const TQString name = m_gui->listView->currentItem()->text( 0 ); if( KMessageBox::warningContinueCancel( 0, i18n( "Are you sure you want to uninstall the script '%1'?" ).arg( name ), i18n("Uninstall Script"), i18n("Uninstall") ) == KMessageBox::Cancel ) return; if( m_scripts.find( name ) == m_scripts.end() ) return; KURL scriptDirURL( m_scripts[name].url.upURL() ); // find if the script is installed in the global or local scripts directory KURL scriptsDirURL; TQStringList dirs = KGlobal::dirs()->findDirs( "data", "amarok/scripts/" ); for ( TQStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it ) { scriptsDirURL = KURL::fromPathOrURL( *it ); if ( scriptsDirURL.isParentOf( scriptDirURL ) ) break; } // find the begining of this script directory tree KURL scriptDirUpURL = scriptDirURL.upURL(); while ( ! scriptsDirURL.equals( scriptDirUpURL, true ) && scriptsDirURL.isParentOf( scriptDirUpURL ) ) { scriptDirURL = scriptDirUpURL; scriptDirUpURL = scriptDirURL.upURL(); } // Delete script directory recursively if( !KIO::NetAccess::del( scriptDirURL, 0 ) ) { KMessageBox::sorry( 0, i18n( "

Could not uninstall this script.

The ScriptManager can only uninstall scripts which have been installed as packages.

" ) ); // only true when not running as root (which is reasonable) return; } TQStringList keys; // Find all scripts that were in the uninstalled directory { foreachType( ScriptMap, m_scripts ) if( scriptDirURL.isParentOf( it.data().url ) ) keys << it.key(); } // Terminate script processes, remove entries from script list { foreach( keys ) { delete m_scripts[*it].li; terminateProcess( &m_scripts[*it].process ); m_scripts.erase( *it ); } } } bool ScriptManager::slotRunScript( bool silent ) { if( !m_gui->runButton->isEnabled() ) return false; TQListViewItem* const li = m_gui->listView->currentItem(); const TQString name = li->text( 0 ); if( m_scripts[name].type == "lyrics" && lyricsScriptRunning() != TQString() ) { if( !silent ) KMessageBox::sorry( 0, i18n( "Another lyrics script is already running. " "You may only run one lyrics script at a time." ) ); return false; } if( m_scripts[name].type == "transcode" && transcodeScriptRunning() != TQString() ) { if( !silent ) KMessageBox::sorry( 0, i18n( "Another transcode script is already running. " "You may only run one transcode script at a time." ) ); return false; } // Don't start a script twice if( m_scripts[name].process ) return false; Amarok::ProcIO* script = new Amarok::ProcIO(); script->setComm( static_cast( KProcess::All ) ); const KURL url = m_scripts[name].url; *script << url.path(); script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) ); connect( script, TQT_SIGNAL( receivedStderr( KProcess*, char*, int ) ), TQT_SLOT( slotReceivedStderr( KProcess*, char*, int ) ) ); connect( script, TQT_SIGNAL( receivedStdout( KProcess*, char*, int ) ), TQT_SLOT( slotReceivedStdout( KProcess*, char*, int ) ) ); connect( script, TQT_SIGNAL( processExited( KProcess* ) ), TQT_SLOT( scriptFinished( KProcess* ) ) ); if( script->start( KProcess::NotifyOnExit ) ) { if( m_scripts[name].type == "score" && !scoreScriptRunning().isNull() ) { stopScript( scoreScriptRunning() ); m_gui->listView->setCurrentItem( li ); } AmarokConfig::setLastScoreScript( name ); } else { if( !silent ) KMessageBox::sorry( 0, i18n( "

Could not start the script %1.

" "

Please make sure that the file has execute (+x) permissions.

" ).arg( name ) ); delete script; return false; } li->setPixmap( 0, SmallIcon( Amarok::icon( "play" ) ) ); debug() << "Running script: " << url.path() << endl; m_scripts[name].process = script; slotCurrentChanged( m_gui->listView->currentItem() ); if( m_scripts[name].type == "lyrics" ) emit lyricsScriptChanged(); return true; } void ScriptManager::slotStopScript() { TQListViewItem* const li = m_gui->listView->currentItem(); const TQString name = li->text( 0 ); // Just a sanity check if( m_scripts.find( name ) == m_scripts.end() ) return; terminateProcess( &m_scripts[name].process ); m_scripts[name].log = TQString(); slotCurrentChanged( m_gui->listView->currentItem() ); li->setPixmap( 0, TQPixmap() ); } void ScriptManager::slotConfigureScript() { const TQString name = m_gui->listView->currentItem()->text( 0 ); if( !m_scripts[name].process ) return; const KURL url = m_scripts[name].url; TQDir::setCurrent( url.directory() ); m_scripts[name].process->writeStdin( TQString("configure") ); } void ScriptManager::slotAboutScript() { const TQString name = m_gui->listView->currentItem()->text( 0 ); TQFile readme( m_scripts[name].url.directory( false ) + "README" ); TQFile license( m_scripts[name].url.directory( false ) + "COPYING" ); if( !readme.open( IO_ReadOnly ) ) { KMessageBox::sorry( 0, i18n( "There is no information available for this script." ) ); return; } KAboutDialog* about = new KAboutDialog( KAboutDialog::AbtTabbed|KAboutDialog::AbtProduct, TQString(), KDialogBase::Ok, KDialogBase::Ok, this ); kapp->setTopWidget( about ); about->setCaption( kapp->makeStdCaption( i18n( "About %1" ).arg( name ) ) ); about->setProduct( "", "", "", "" ); // Get rid of the confusing KDE version text TQLabel* product = static_cast( TQT_TQWIDGET(about->mainWidget()->child( "version" )) ); if( product ) product->setText( i18n( "%1 Amarok Script" ).arg( name ) ); about->addTextPage( i18n( "About" ), readme.readAll(), true ); if( license.open( IO_ReadOnly ) ) about->addLicensePage( i18n( "License" ), license.readAll() ); about->setInitialSize( TQSize( 500, 350 ) ); about->show(); } void ScriptManager::slotShowContextMenu( TQListViewItem* item, const TQPoint& pos ) { const bool isCategory = item == m_generalCategory || item == m_lyricsCategory || item == m_scoreCategory || item == m_transcodeCategory; if( !item || isCategory ) return; // Look up script entry in our map ScriptMap::Iterator it; ScriptMap::Iterator end( m_scripts.end() ); for( it = m_scripts.begin(); it != end; ++it ) if( it.data().li == item ) break; enum { SHOW_LOG, EDIT }; KPopupMenu menu; menu.insertTitle( i18n( "Debugging" ) ); menu.insertItem( SmallIconSet( Amarok::icon( "clock" ) ), i18n( "Show Output &Log" ), SHOW_LOG ); menu.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), i18n( "&Edit" ), EDIT ); menu.setItemEnabled( SHOW_LOG, it.data().process ); const int id = menu.exec( pos ); switch( id ) { case EDIT: KRun::runCommand( "kwrite " + KProcess::quote(it.data().url.path()) ); break; case SHOW_LOG: TQString line; while( it.data().process->readln( line ) != -1 ) it.data().log += line; KTextEdit* editor = new KTextEdit( it.data().log ); kapp->setTopWidget( editor ); editor->setCaption( kapp->makeStdCaption( i18n( "Output Log for %1" ).arg( it.key() ) ) ); editor->setReadOnly( true ); TQFont font( "fixed" ); font.setFixedPitch( true ); font.setStyleHint( TQFont::TypeWriter ); editor->setFont( font ); editor->setTextFormat( TQTextEdit::PlainText ); editor->resize( 500, 380 ); editor->show(); break; } } /* This is just a workaround, some scripts crash for some people if stdout is not handled. */ void ScriptManager::slotReceivedStdout( KProcess*, char* buf, int len ) { debug() << TQString::fromLatin1( buf, len ) << endl; } void ScriptManager::slotReceivedStderr( KProcess* process, char* buf, int len ) { // Look up script entry in our map ScriptMap::Iterator it; ScriptMap::Iterator end( m_scripts.end() ); for( it = m_scripts.begin(); it != end; ++it ) if( it.data().process == process ) break; const TQString text = TQString::fromLatin1( buf, len ); error() << it.key() << ":\n" << text << endl; if( it.data().log.length() > 20000 ) it.data().log = "==== LOG TRUNCATED HERE ====\n"; it.data().log += text; } void ScriptManager::scriptFinished( KProcess* process ) //SLOT { // Look up script entry in our map ScriptMap::Iterator it; ScriptMap::Iterator end( m_scripts.end() ); for( it = m_scripts.begin(); it != end; ++it ) if( it.data().process == process ) break; // Check if there was an error on exit if( process->normalExit() && process->exitStatus() != 0 ) KMessageBox::detailedError( 0, i18n( "The script '%1' exited with error code: %2" ) .arg( it.key() ).arg( process->exitStatus() ) ,it.data().log ); // Destroy script process delete it.data().process; it.data().process = 0; it.data().log = TQString(); it.data().li->setPixmap( 0, TQPixmap() ); slotCurrentChanged( m_gui->listView->currentItem() ); } //////////////////////////////////////////////////////////////////////////////// // private //////////////////////////////////////////////////////////////////////////////// TQStringList ScriptManager::scriptsOfType( const TQString &type ) const { TQStringList scripts; foreachType( ScriptMap, m_scripts ) if( it.data().type == type ) scripts += it.key(); return scripts; } TQString ScriptManager::scriptRunningOfType( const TQString &type ) const { foreachType( ScriptMap, m_scripts ) if( it.data().process ) if( it.data().type == type ) return it.key(); return TQString(); } TQString ScriptManager::ensureScoreScriptRunning() { TQString s = scoreScriptRunning(); if( !s.isNull() ) return s; if( runScript( AmarokConfig::lastScoreScript(), true /*silent*/ ) ) return AmarokConfig::lastScoreScript(); const TQString def = i18n( "Score" ) + ": " + "Default"; if( runScript( def, true ) ) return def; const TQStringList scripts = scoreScripts(); for( TQStringList::const_iterator it = scripts.begin(), end = scripts.end(); it != end; ++it ) if( runScript( *it, true ) ) return *it; return TQString(); } void ScriptManager::terminateProcess( KProcIO** proc ) { if( *proc ) { (*proc)->kill(); // Sends SIGTERM (*proc)->detach(); delete *proc; *proc = 0; } } void ScriptManager::notifyScripts( const TQString& message ) { foreachType( ScriptMap, m_scripts ) { KProcIO* const proc = it.data().process; if( proc ) proc->writeStdin( message ); } } void ScriptManager::loadScript( const TQString& path ) { if( !path.isEmpty() ) { const KURL url = KURL::fromPathOrURL( path ); TQString name = url.fileName(); TQString type = "generic"; // Read and parse .spec file, if exists TQFileInfo info( path ); KListViewItem* li = 0; const TQString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec"; if( TQFile::exists( specPath ) ) { KConfig spec( specPath, true, false ); if( spec.hasKey( "name" ) ) name = spec.readEntry( "name" ); if( spec.hasKey( "type" ) ) { type = spec.readEntry( "type" ); if( type == "lyrics" ) li = new KListViewItem( m_lyricsCategory, name ); if( type == "transcode" ) li = new KListViewItem( m_transcodeCategory, name ); if( type == "score" ) li = new KListViewItem( m_scoreCategory, name ); } } if( !li ) li = new KListViewItem( m_generalCategory, name ); li->setPixmap( 0, TQPixmap() ); ScriptItem item; item.url = url; item.type = type; item.process = 0; item.li = li; m_scripts[name] = item; debug() << "Loaded: " << name << endl; slotCurrentChanged( m_gui->listView->currentItem() ); } } void ScriptManager::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) { switch( state ) { case Engine::Empty: notifyScripts( "engineStateChange: empty" ); break; case Engine::Idle: notifyScripts( "engineStateChange: idle" ); break; case Engine::Paused: notifyScripts( "engineStateChange: paused" ); break; case Engine::Playing: notifyScripts( "engineStateChange: playing" ); break; } } void ScriptManager::engineNewMetaData( const MetaBundle& /*bundle*/, bool /*trackChanged*/ ) { notifyScripts( "trackChange" ); } void ScriptManager::engineVolumeChanged( int newVolume ) { notifyScripts( "volumeChange: " + TQString::number( newVolume ) ); } #include "scriptmanager.moc"