/*
 *
 * $Id: k3bdefaultexternalprograms.cpp 731898 2007-11-02 08:22:18Z trueg $
 * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 *
 * 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.
 * See the file "COPYING" for the exact licensing terms.
 */


#include "k3bdefaultexternalprograms.h"
#include "k3bexternalbinmanager.h"
#include <k3bglobals.h>

#include <tqfile.h>
#include <tqdir.h>
#include <tqfileinfo.h>
#include <tqobject.h>
#include <tqregexp.h>
#include <tqtextstream.h>

#include <k3bprocess.h>
#include <kdebug.h>

#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>



void K3b::addDefaultPrograms( K3bExternalBinManager* m )
{
  m->addProgram( new K3bCdrecordProgram(false) );
  m->addProgram( new K3bMkisofsProgram() );
  m->addProgram( new K3bReadcdProgram() );
  m->addProgram( new K3bCdrdaoProgram() );
  m->addProgram( new K3bGrowisofsProgram() );
  m->addProgram( new K3bDvdformatProgram() );
  //  m->addProgram( new K3bDvdBooktypeProgram() );
}


void K3b::addTranscodePrograms( K3bExternalBinManager* m )
{
  static const char* transcodeTools[] =  { "transcode",
					   0, // K3b 1.0 only uses the transcode binary
					   "tcprobe",
					   "tccat",
					   "tcscan",
					   "tcextract",
					   "tcdecode",
					   0 };

  for( int i = 0; transcodeTools[i]; ++i )
    m->addProgram( new K3bTranscodeProgram( transcodeTools[i] ) );
}


void K3b::addVcdimagerPrograms( K3bExternalBinManager* m )
{
  // don't know if we need more vcdTools in the future (vcdxrip)
  static const char* vcdTools[] =  { "vcdxbuild",
                                     "vcdxminfo",
                                     "vcdxrip",
                                     0 };

  for( int i = 0; vcdTools[i]; ++i )
    m->addProgram( new K3bVcdbuilderProgram( vcdTools[i] ) );
}


K3bCdrecordProgram::K3bCdrecordProgram( bool dvdPro )
  : K3bExternalProgram( dvdPro ? "cdrecord-prodvd" : "cdrecord" ),
    m_dvdPro(dvdPro)
{
}


//
// This is a hack for Debian based systems which use
// a wrapper cdrecord script to call cdrecord.mmap or cdrecord.shm
// depending on the kernel version.
// For 2.0.x and 2.2.x kernels the shm version is used. In all
// other cases it's the mmap version.
//
// But since it may be that someone manually installed cdrecord
// replacing the wrapper we check if cdrecord is a script.
//
static TQString& debianWeirdnessHack( TQString& path )
{
  if( TQFile::exists( path + ".mmap" ) ) {
    kdDebug() << "(K3bCdrecordProgram) checking for Debian cdrecord wrapper script." << endl;
    if( TQFileInfo( path ).size() < 1024 ) {
      kdDebug() << "(K3bCdrecordProgram) Debian Wrapper script size fits. Checking file." << endl;
      TQFile f( path );
      f.open( IO_ReadOnly );
      TQString s = TQTextStream( &f ).read();
      if( s.contains( "cdrecord.mmap" ) && s.contains( "cdrecord.shm" ) ) {
	kdDebug() << "(K3bCdrecordProgram) Found Debian Wrapper script." << endl;
	TQString ext;
	if( K3b::kernelVersion().versionString().left(3) > "2.2" )
	  ext = ".mmap";
	else
	  ext = ".shm";

	kdDebug() << "(K3bCdrecordProgram) Using cdrecord" << ext << endl;

	path += ext;
      }
    }
  }

  return path;
}


bool K3bCdrecordProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  bool wodim = false;
  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");

    if( TQFile::exists( path + "wodim" ) ) {
      wodim = true;
      path += "wodim";
    }
    else if( TQFile::exists( path + "cdrecord" ) ) {
      path += "cdrecord";
    }
    else
      return false;
  }

  debianWeirdnessHack( path );

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path << "-version";
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = -1;
    if( wodim ) {
      pos = out.output().find( "Wodim" );
    }
    else if( m_dvdPro ) {
      pos = out.output().find( "Cdrecord-ProDVD" );
    }
    else {
      pos = out.output().find( "Cdrecord" );
    }

    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("[0-9]"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( TQRegExp("\\s"), pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    if( wodim )
      bin->addFeature( "wodim" );

    pos = out.output().find( "Copyright") + 14;
    endPos = out.output().find( "\n", pos );

    // cdrecord does not use local encoding for the copyright statement but plain latin1
    bin->copyright = TQString::fromLatin1( out.output().mid( pos, endPos-pos ).local8Bit() ).stripWhiteSpace();
  }
  else {
    kdDebug() << "(K3bCdrecordProgram) could not start " << path << endl;
    return false;
  }

  if( !m_dvdPro && bin->version.suffix().endsWith( "-dvd" ) ) {
    bin->addFeature( "dvd-patch" );
    bin->version = TQString(bin->version.versionString()).remove("-dvd");
  }

  // probe features
  KProcess fp;
  out.setProcess( &fp );
  fp << path << "-help";
  if( fp.start( KProcess::Block, KProcess::AllOutput ) ) {
    if( out.output().contains( "gracetime" ) )
      bin->addFeature( "gracetime" );
    if( out.output().contains( "-overburn" ) )
      bin->addFeature( "overburn" );
    if( out.output().contains( "-text" ) )
      bin->addFeature( "cdtext" );
    if( out.output().contains( "-clone" ) )
      bin->addFeature( "clone" );
    if( out.output().contains( "-tao" ) )
      bin->addFeature( "tao" );
    if( out.output().contains( "cuefile=" ) &&
	( wodim || bin->version > K3bVersion( 2, 1, -1, "a14") ) ) // cuefile handling was still buggy in a14
      bin->addFeature( "cuefile" );

    // new mode 2 options since cdrecord 2.01a12
    // we use both checks here since the help was not updated in 2.01a12 yet (well, I
    // just double-checked and the help page is proper but there is no harm in having
    // two checks)
    // and the version check does not handle versions like 2.01-dvd properly
    if( out.output().contains( "-xamix" ) ||
	bin->version >= K3bVersion( 2, 1, -1, "a12" ) ||
	wodim )
      bin->addFeature( "xamix" );

    // check if we run cdrecord as root
    struct stat s;
    if( !::stat( TQFile::encodeName(path), &s ) ) {
      if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
	bin->addFeature( "suidroot" );
    }
  }
  else {
    kdDebug() << "(K3bCdrecordProgram) could not start " << bin->path << endl;
    delete bin;
    return false;
  }

  if( bin->version < K3bVersion( 2, 0 ) && !wodim )
    bin->addFeature( "outdated" );

  // FIXME: are these version correct?
  if( bin->version >= K3bVersion("1.11a38") || wodim )
    bin->addFeature( "plain-atapi" );
  if( bin->version > K3bVersion("1.11a17") || wodim )
    bin->addFeature( "hacked-atapi" );

  if( bin->version >= K3bVersion( 2, 1, 1, "a02" ) || wodim )
    bin->addFeature( "short-track-raw" );

  if( bin->version >= K3bVersion( 2, 1, -1, "a13" ) || wodim )
    bin->addFeature( "audio-stdin" );

  if( bin->version >= K3bVersion( "1.11a02" ) || wodim )
    bin->addFeature( "burnfree" );
  else
    bin->addFeature( "burnproof" );

  addBin( bin );
  return true;
}



K3bMkisofsProgram::K3bMkisofsProgram()
  : K3bExternalProgram( "mkisofs" )
{
}

bool K3bMkisofsProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  bool genisoimage = false;
  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");

    if( TQFile::exists( path + "genisoimage" ) ) {
      genisoimage = true;
      path += "genisoimage";
    }
    else if( TQFile::exists( path + "mkisofs" ) ) {
      path += "mkisofs";
    }
    else
      return false;
  }

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  vp << path << "-version";
  K3bProcessOutputCollector out( &vp );
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = -1;
    if( genisoimage )
      pos = out.output().find( "genisoimage" );
    else
      pos = out.output().find( "mkisofs" );

    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("[0-9]"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( ' ', pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    if( genisoimage )
      bin->addFeature( "genisoimage" );
  }
  else {
    kdDebug() << "(K3bMkisofsProgram) could not start " << path << endl;
    return false;
  }



  // probe features
  KProcess fp;
  fp << path << "-help";
  out.setProcess( &fp );
  if( fp.start( KProcess::Block, KProcess::AllOutput ) ) {
    if( out.output().contains( "-udf" ) )
      bin->addFeature( "udf" );
    if( out.output().contains( "-dvd-video" ) )
      bin->addFeature( "dvd-video" );
    if( out.output().contains( "-joliet-long" ) )
      bin->addFeature( "joliet-long" );
    if( out.output().contains( "-xa" ) )
      bin->addFeature( "xa" );
    if( out.output().contains( "-sectype" ) )
      bin->addFeature( "sectype" );

    // check if we run mkisofs as root
    struct stat s;
    if( !::stat( TQFile::encodeName(path), &s ) ) {
      if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
	bin->addFeature( "suidroot" );
    }
  }
  else {
    kdDebug() << "(K3bMkisofsProgram) could not start " << bin->path << endl;
    delete bin;
    return false;
  }

  if( bin->version < K3bVersion( 1, 14) && !genisoimage )
    bin->addFeature( "outdated" );

  if( bin->version >= K3bVersion( 1, 15, -1, "a40" ) || genisoimage )
    bin->addFeature( "backslashed_filenames" );

  if ( genisoimage && bin->version >= K3bVersion( 1, 1, 4 ) )
      bin->addFeature( "no-4gb-limit" );

  if ( !genisoimage && bin->version >= K3bVersion( 2, 1, 1, "a32" ) )
      bin->addFeature( "no-4gb-limit" );

  addBin(bin);
  return true;
}


K3bReadcdProgram::K3bReadcdProgram()
  : K3bExternalProgram( "readcd" )
{
}

bool K3bReadcdProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  bool readom = false;
  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");

    if( TQFile::exists( path + "readom" ) ) {
      readom = true;
      path += "readom";
    }
    else if( TQFile::exists( path + "readcd" ) ) {
      path += "readcd";
    }
    else
      return false;
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  vp << path << "-version";
  K3bProcessOutputCollector out( &vp );
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = -1;
    if( readom )
      pos = out.output().find( "readom" );
    else
      pos = out.output().find( "readcd" );
    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("[0-9]"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( ' ', pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    if( readom )
      bin->addFeature( "readom" );
  }
  else {
    kdDebug() << "(K3bMkisofsProgram) could not start " << path << endl;
    return false;
  }



  // probe features
  KProcess fp;
  fp << path << "-help";
  out.setProcess( &fp );
  if( fp.start( KProcess::Block, KProcess::AllOutput ) ) {
    if( out.output().contains( "-clone" ) )
      bin->addFeature( "clone" );

    // check if we run mkisofs as root
    struct stat s;
    if( !::stat( TQFile::encodeName(path), &s ) ) {
      if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
	bin->addFeature( "suidroot" );
    }
  }
  else {
    kdDebug() << "(K3bReadcdProgram) could not start " << bin->path << endl;
    delete bin;
    return false;
  }


  // FIXME: are these version correct?
  if( bin->version >= K3bVersion("1.11a38") || readom )
    bin->addFeature( "plain-atapi" );
  if( bin->version > K3bVersion("1.11a17") || readom )
    bin->addFeature( "hacked-atapi" );

  addBin(bin);
  return true;
}


K3bCdrdaoProgram::K3bCdrdaoProgram()
  : K3bExternalProgram( "cdrdao" )
{
}

bool K3bCdrdaoProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("cdrdao");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  vp << path ;
  K3bProcessOutputCollector out( &vp );
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "Cdrdao version" );
    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("[0-9]"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( ' ', pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    pos = out.output().find( "(C)", endPos+1 ) + 4;
    endPos = out.output().find( '\n', pos );
    bin->copyright = out.output().mid( pos, endPos-pos );
  }
  else {
    kdDebug() << "(K3bCdrdaoProgram) could not start " << path << endl;
    return false;
  }



  // probe features
  KProcess fp;
  fp << path << "write" << "-h";
  out.setProcess( &fp );
  if( fp.start( KProcess::Block, KProcess::AllOutput ) ) {
    if( out.output().contains( "--overburn" ) )
      bin->addFeature( "overburn" );
    if( out.output().contains( "--multi" ) )
      bin->addFeature( "multisession" );

    if( out.output().contains( "--buffer-under-run-protection" ) )
      bin->addFeature( "disable-burnproof" );

    // check if we run cdrdao as root
    struct stat s;
    if( !::stat( TQFile::encodeName(path), &s ) ) {
      if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
	bin->addFeature( "suidroot" );
    }
  }
  else {
    kdDebug() << "(K3bCdrdaoProgram) could not start " << bin->path << endl;
    delete bin;
    return false;
  }


  // SuSE 9.0 ships with a patched cdrdao 1.1.7 which contains an updated libschily
  // Gentoo ships with a patched cdrdao 1.1.7 which contains scglib support
  if( bin->version > K3bVersion( 1, 1, 7 ) ||
      bin->version == K3bVersion( 1, 1, 7, "-gentoo" ) ||
      bin->version == K3bVersion( 1, 1, 7, "-suse" ) ) {
    //    bin->addFeature( "plain-atapi" );
    bin->addFeature( "hacked-atapi" );
  }

  if( bin->version >= K3bVersion( 1, 1, 8 ) )
    bin->addFeature( "plain-atapi" );

  addBin(bin);
  return true;
}


K3bTranscodeProgram::K3bTranscodeProgram( const TQString& transcodeProgram )
  : K3bExternalProgram( transcodeProgram ),
    m_transcodeProgram( transcodeProgram )
{
}

bool K3bTranscodeProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  if( path[path.length()-1] != '/' )
    path.append("/");

  TQString appPath = path + m_transcodeProgram;

  if( !TQFile::exists( appPath ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  vp << appPath << "-v";
  K3bProcessOutputCollector out( &vp );
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "transcode v" );
    if( pos < 0 )
      return false;

    pos += 11;

    int endPos = out.output().find( TQRegExp("[\\s\\)]"), pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = appPath;
    bin->version = out.output().mid( pos, endPos-pos );
  }
  else {
    kdDebug() << "(K3bTranscodeProgram) could not start " << appPath << endl;
    return false;
  }

  //
  // Check features
  //
  TQString modInfoBin = path + "tcmodinfo";
  KProcess modp;
  modp << modInfoBin << "-p";
  out.setProcess( &modp );
  if( modp.start( KProcess::Block, KProcess::AllOutput ) ) {
    TQString modPath = out.output().stripWhiteSpace();
    TQDir modDir( modPath );
    if( !modDir.entryList( "*export_xvid*", TQDir::Files ).isEmpty() )
      bin->addFeature( "xvid" );
    if( !modDir.entryList( "*export_lame*", TQDir::Files ).isEmpty() )
      bin->addFeature( "lame" );
    if( !modDir.entryList( "*export_ffmpeg*", TQDir::Files ).isEmpty() )
      bin->addFeature( "ffmpeg" );
    if( !modDir.entryList( "*export_ac3*", TQDir::Files ).isEmpty() )
      bin->addFeature( "ac3" );
  }

  addBin(bin);
  return true;
}



K3bVcdbuilderProgram::K3bVcdbuilderProgram( const TQString& p )
  : K3bExternalProgram( p ),
    m_vcdbuilderProgram( p )
{
}

bool K3bVcdbuilderProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append(m_vcdbuilderProgram);
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  vp << path << "-V";
  K3bProcessOutputCollector out( &vp );
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "GNU VCDImager" );
    if( pos < 0 )
      return false;

    pos += 14;

    int endPos = out.output().find( TQRegExp("[\\n\\)]"), pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos ).stripWhiteSpace();

    pos = out.output().find( "Copyright" ) + 14;
    endPos = out.output().find( "\n", pos );
    bin->copyright = out.output().mid( pos, endPos-pos ).stripWhiteSpace();
  }
  else {
    kdDebug() << "(K3bVcdbuilderProgram) could not start " << path << endl;
    return false;
  }

  addBin(bin);
  return true;
}


K3bNormalizeProgram::K3bNormalizeProgram()
  : K3bExternalProgram( "normalize-audio" )
{
}


bool K3bNormalizeProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("normalize-audio");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path << "--version";
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "normalize" );
    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("\\d"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( TQRegExp("\\s"), pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    pos = out.output().find( "Copyright" )+14;
    endPos = out.output().find( "\n", pos );
    bin->copyright = out.output().mid( pos, endPos-pos ).stripWhiteSpace();
  }
  else {
    kdDebug() << "(K3bCdrecordProgram) could not start " << path << endl;
    return false;
  }

  addBin( bin );
  return true;
}


K3bGrowisofsProgram::K3bGrowisofsProgram()
  : K3bExternalProgram( "growisofs" )
{
}

bool K3bGrowisofsProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("growisofs");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path << "-version";
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "growisofs" );
    if( pos < 0 )
      return false;

    pos = out.output().find( TQRegExp("\\d"), pos );
    if( pos < 0 )
      return false;

    int endPos = out.output().find( ",", pos+1 );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );
  }
  else {
    kdDebug() << "(K3bGrowisofsProgram) could not start " << path << endl;
    return false;
  }

  // fixed Copyright:
  bin->copyright = "Andy Polyakov <appro@fy.chalmers.se>";

  // check if we run growisofs as root
  struct stat s;
  if( !::stat( TQFile::encodeName(path), &s ) ) {
    if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
      bin->addFeature( "suidroot" );
  }

  addBin( bin );
  return true;
}


K3bDvdformatProgram::K3bDvdformatProgram()
  : K3bExternalProgram( "dvd+rw-format" )
{
}

bool K3bDvdformatProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("dvd+rw-format");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path;
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    // different locales make searching for the +- char difficult
    // so we simply ignore it.
    int pos = out.output().find( TQRegExp("DVD.*RAM format utility") );
    if( pos < 0 )
      return false;

    pos = out.output().find( "version", pos );
    if( pos < 0 )
      return false;

    pos += 8;

    // the version ends in a dot.
    int endPos = out.output().find( TQRegExp("\\.\\D"), pos );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );
  }
  else {
    kdDebug() << "(K3bDvdformatProgram) could not start " << path << endl;
    return false;
  }

  // fixed Copyright:
  bin->copyright = "Andy Polyakov <appro@fy.chalmers.se>";

  // check if we run dvd+rw-format as root
  struct stat s;
  if( !::stat( TQFile::encodeName(path), &s ) ) {
    if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
      bin->addFeature( "suidroot" );
  }

  addBin( bin );
  return true;
}


K3bDvdBooktypeProgram::K3bDvdBooktypeProgram()
  : K3bExternalProgram( "dvd+rw-booktype" )
{
}

bool K3bDvdBooktypeProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("dvd+rw-booktype");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path;
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "dvd+rw-booktype" );
    if( pos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    // No version information. Create dummy version
    bin->version = K3bVersion( 1, 0, 0 );
  }
  else {
    kdDebug() << "(K3bDvdBooktypeProgram) could not start " << path << endl;
    return false;
  }

  addBin( bin );
  return true;
}



K3bCdda2wavProgram::K3bCdda2wavProgram()
  : K3bExternalProgram( "cdda2wav" )
{
}

bool K3bCdda2wavProgram::scan( const TQString& p )
{
  if( p.isEmpty() )
    return false;

  TQString path = p;
  TQFileInfo fi( path );
  if( fi.isDir() ) {
    if( path[path.length()-1] != '/' )
      path.append("/");
    path.append("cdda2wav");
  }

  if( !TQFile::exists( path ) )
    return false;

  K3bExternalBin* bin = 0;

  // probe version
  KProcess vp;
  K3bProcessOutputCollector out( &vp );

  vp << path << "-h";
  if( vp.start( KProcess::Block, KProcess::AllOutput ) ) {
    int pos = out.output().find( "cdda2wav" );
    if( pos < 0 )
      return false;

    pos = out.output().find( "Version", pos );
    if( pos < 0 )
      return false;

    pos += 8;

    // the version does not end in a space but the kernel info
    int endPos = out.output().find( TQRegExp("[^\\d\\.]"), pos );
    if( endPos < 0 )
      return false;

    bin = new K3bExternalBin( this );
    bin->path = path;
    bin->version = out.output().mid( pos, endPos-pos );

    // features (we do this since the cdda2wav help says that the short
    //           options will disappear soon)
    if( out.output().find( "-info-only" ) )
      bin->addFeature( "info-only" ); // otherwise use the -J option
    if( out.output().find( "-no-infofile" ) )
      bin->addFeature( "no-infofile" ); // otherwise use the -H option
    if( out.output().find( "-gui" ) )
      bin->addFeature( "gui" ); // otherwise use the -g option
    if( out.output().find( "-bulk" ) )
      bin->addFeature( "bulk" ); // otherwise use the -B option
    if( out.output().find( "dev=" ) )
      bin->addFeature( "dev" ); // otherwise use the -B option
  }
  else {
    kdDebug() << "(K3bCdda2wavProgram) could not start " << path << endl;
    return false;
  }

  // check if we run as root
  struct stat s;
  if( !::stat( TQFile::encodeName(path), &s ) ) {
    if( (s.st_mode & S_ISUID) && s.st_uid == 0 )
      bin->addFeature( "suidroot" );
  }

  addBin( bin );
  return true;
}