You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
953 lines
31 KiB
953 lines
31 KiB
/***************************************************************************
|
|
* Copyright (C) 2004-2006 by Mark Kretschmann <markey@web.de> *
|
|
* 2005 by Seb Ruiz <me@sebruiz.net> *
|
|
* 2006 by Alexandre Oliveira <aleprj@gmail.com> *
|
|
* 2006 by Martin Ellis <martin.ellis@kdemail.net> *
|
|
* *
|
|
* 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 <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <tqcheckbox.h>
|
|
#include <tqdir.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqfont.h>
|
|
#include <tqlabel.h>
|
|
#include <tqtextcodec.h>
|
|
#include <tqtimer.h>
|
|
|
|
#include <tdeaboutdialog.h>
|
|
#include <tdeapplication.h>
|
|
#include <tdefiledialog.h>
|
|
#include <kiconloader.h>
|
|
#include <tdeio/netaccess.h>
|
|
#include <tdelistview.h>
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdepopupmenu.h>
|
|
#include <kprocio.h>
|
|
#include <tdeprotocolmanager.h>
|
|
#include <kpushbutton.h>
|
|
#include <krun.h>
|
|
#include <kstandarddirs.h>
|
|
#include <ktar.h>
|
|
#include <ktextedit.h>
|
|
#include <twin.h>
|
|
|
|
#include <knewstuff/downloaddialog.h> // knewstuff script fetching
|
|
#include <knewstuff/engine.h> // "
|
|
#include <knewstuff/knewstuff.h> // "
|
|
#include <knewstuff/provider.h> // "
|
|
|
|
|
|
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 TDEListViewItem( m_gui->listView, i18n( "General" ) );
|
|
m_lyricsCategory = new TDEListViewItem( m_gui->listView, i18n( "Lyrics" ) );
|
|
m_scoreCategory = new TDEListViewItem( m_gui->listView, i18n( "Score" ) );
|
|
m_transcodeCategory = new TDEListViewItem( 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
|
|
TDEConfig* 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
|
|
TDEConfig* 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:
|
|
|
|
TDEConfig* 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( "<p>Script installation failed.</p>"
|
|
"<p>The package did not contain an executable file. "
|
|
"Please inform the package maintainer about this error.</p>" ) );
|
|
|
|
// Delete directory recursively
|
|
TDEIO::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<const KArchiveDirectory*>( 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 = TDEGlobal::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( !TDEIO::NetAccess::del( scriptDirURL, 0 ) ) {
|
|
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>" ) ); // 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<TDEProcess::Communication>( TDEProcess::All ) );
|
|
const KURL url = m_scripts[name].url;
|
|
*script << url.path();
|
|
script->setWorkingDirectory( Amarok::saveLocation( "scripts-data/" ) );
|
|
|
|
connect( script, TQT_SIGNAL( receivedStderr( TDEProcess*, char*, int ) ), TQT_SLOT( slotReceivedStderr( TDEProcess*, char*, int ) ) );
|
|
connect( script, TQT_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ), TQT_SLOT( slotReceivedStdout( TDEProcess*, char*, int ) ) );
|
|
connect( script, TQT_SIGNAL( processExited( TDEProcess* ) ), TQT_SLOT( scriptFinished( TDEProcess* ) ) );
|
|
|
|
if( script->start( TDEProcess::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( "<p>Could not start the script <i>%1</i>.</p>"
|
|
"<p>Please make sure that the file has execute (+x) permissions.</p>" ).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;
|
|
}
|
|
|
|
TDEAboutDialog* about = new TDEAboutDialog( TDEAboutDialog::AbtTabbed|TDEAboutDialog::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<TQLabel*>( 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 };
|
|
TDEPopupMenu 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 " + TDEProcess::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( TDEProcess*, char* buf, int len )
|
|
{
|
|
debug() << TQString::fromLatin1( buf, len ) << endl;
|
|
}
|
|
|
|
|
|
void
|
|
ScriptManager::slotReceivedStderr( TDEProcess* 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( TDEProcess* 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 );
|
|
TDEListViewItem* li = 0;
|
|
const TQString specPath = info.dirPath() + '/' + info.baseName( true ) + ".spec";
|
|
if( TQFile::exists( specPath ) ) {
|
|
TDEConfig 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 TDEListViewItem( m_lyricsCategory, name );
|
|
if( type == "transcode" )
|
|
li = new TDEListViewItem( m_transcodeCategory, name );
|
|
if( type == "score" )
|
|
li = new TDEListViewItem( m_scoreCategory, name );
|
|
}
|
|
}
|
|
|
|
if( !li )
|
|
li = new TDEListViewItem( 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"
|