/***************************************************************************
    smb4kscanner.cpp  -  The network scan core class of Smb4K.
                             -------------------
    begin                : Sam Mai 31 2003
    copyright            : (C) 2003-2008 by Alexander Reinholdt
    email                : dustpuppy@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *   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., 59 Temple Place, Suite 330, Boston,   *
 *   MA  02111-1307 USA                                                    *
 ***************************************************************************/

// Qt includes
#include <QApplication>
#include <QMap>
#include <QHostAddress>
#include <QAbstractSocket>
#include <QDesktopWidget>

// KDE includes
#include <klocale.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kshell.h>

// system includes
#include <stdlib.h>

// Application specific includes.
#include <smb4kscanner.h>
#include <smb4kscanner_p.h>
#include <smb4kauthinfo.h>
#include <smb4kcoremessage.h>
#include <smb4kglobal.h>
#include <smb4ksambaoptionshandler.h>
#include <smb4ksettings.h>
#include <smb4kworkgroup.h>
#include <smb4khost.h>
#include <smb4kshare.h>
#include <smb4kipaddressscanner.h>
#include <smb4khomesshareshandler.h>
#include <smb4kwalletmanager.h>

using namespace Smb4KGlobal;

typedef Smb4KScannerQueueContainer QueueContainer;


Smb4KScanner::Smb4KScanner( QObject *parent )
: QObject( parent )
{
  m_priv = new Smb4KScannerPrivate;

  m_proc = new KProcess( this );

  m_working = false;
  m_aborted = false;
  m_process_error = (QProcess::ProcessError)(-1);

  connect( m_proc,  SIGNAL( finished( int, QProcess::ExitStatus ) ),
           this,    SLOT( slotProcessFinished( int, QProcess::ExitStatus ) )  );

  connect( m_proc,  SIGNAL( error( QProcess::ProcessError ) ),
           this,    SLOT( slotProcessError( QProcess::ProcessError ) ) );

  connect( Smb4KIPAddressScanner::self(), SIGNAL( ipAddress( Smb4KHost * ) ),
           this,                          SIGNAL( ipAddress( Smb4KHost * ) ) );
}


Smb4KScanner::~Smb4KScanner()
{
  abort();

  delete m_priv;
}


void Smb4KScanner::init()
{
  startTimer( TIMER_INTERVAL );

  rescan();
}


void Smb4KScanner::abort()
{
  while ( !m_queue.isEmpty() )
  {
    m_queue.dequeue();
  }

  if ( m_proc->state() == QProcess::Running )
  {
    m_proc->kill();
  }

  m_aborted = true;
}


void Smb4KScanner::rescan()
{
  m_queue.enqueue( QueueContainer( Init ) );
}


void Smb4KScanner::getWorkgroupMembers( Smb4KWorkgroup *workgroup )
{
  m_queue.enqueue( QueueContainer( Hosts, *workgroup ) );
}


void Smb4KScanner::getShares( Smb4KHost *host )
{
  m_queue.enqueue( QueueContainer( Shares, *host ) );
}


void Smb4KScanner::getInfo( Smb4KHost *host )
{
  Smb4KHost *known_host = findHost( host->name(), host->workgroup() );

  if ( known_host && known_host->infoChecked() )
  {
    emit info( known_host );

    return;
  }
  else
  {
    // Don't let Smb4K check this host several times. This will
    // be reset in processInfo() if necessary.
    host->setInfo( QString(), QString() );
  }

  m_queue.enqueue( QueueContainer( Info, *host ) );
}


void Smb4KScanner::lookupDomains()
{
  // Abort any process that is still running.
  abort();

  // Assemble the command.
  QString command;

  command.append( "nmblookup -M " );
  command.append( Smb4KSambaOptionsHandler::self()->nmblookupOptions() );
  command.append( " -- - | grep '<01>' | awk '{print $1}'" );
  command.append( !Smb4KSambaOptionsHandler::self()->winsServer().isEmpty() ?
                  QString( " | xargs -Iips nmblookup -R -U %1 -A ips" ).arg( Smb4KSambaOptionsHandler::self()->winsServer() ) :
                  " | xargs -Iips nmblookup -A ips" );
  command.append( Smb4KSambaOptionsHandler::self()->nmblookupOptions() );

  m_proc->setShellCommand( command );

  startProcess( Workgroups );
}


void Smb4KScanner::queryMasterBrowser()
{
  // Abort any process that is still running.
  abort();

  // Assemble the command.
  QString command;

  command.append( "net " );

  if ( Smb4KSettings::queryCurrentMaster() )
  {
    Smb4KWorkgroup workgroup( Smb4KSettings::domainName() );

    command.append( Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::LookupMaster, workgroup ) );
    command.append( " -U % | xargs -Imaster net " );
    command.append( Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::Domain ) );
    command.append( " -U % -S master" );
  }
  else if ( Smb4KSettings::queryCustomMaster() )
  {
    Smb4KHost host( Smb4KSettings::customMasterBrowser() );

    command.append( Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::LookupHost, host ) );
    command.append( " -U % -S "+KShell::quoteArg( host.name() ) );
    command.append( " | xargs -Iip net " );
    command.append( Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::Domain ) );
    command.append( " -U % -S "+KShell::quoteArg( host.name() )+" -I ip" );
  }
  else
  {
    return;
  }

  m_proc->setShellCommand( command );

  startProcess( QueryHost );
}


void Smb4KScanner::scanBroadcastAreas()
{
  // Abort any process that is still running.
  abort();

  // Assemble the command.
  QString command;

  // Get the broadcast addresses that are to be scanned:
  QStringList addresses = Smb4KSettings::broadcastAreas().split( ",", QString::SkipEmptyParts );

  // Build the command:
  for ( int i = 0; i < addresses.size(); ++i )
  {
    command.append( "nmblookup " );
    // We want all globally defined options for nmblookup, except
    // the broadcast address, because that is needed for the IP
    // scan:
    command.append( Smb4KSambaOptionsHandler::self()->nmblookupOptions( false ) );
    command.append( " -B "+addresses.at( i )+" -- '*' " );
    command.append( "| sed -e /querying/d | awk '{print $1}' " );
    command.append( "| xargs -Iip nmblookup " );
    // This time we want to have the globally defined broadcast
    // address:
    command.append( Smb4KSambaOptionsHandler::self()->nmblookupOptions() );
    // Include the WINS server:
    command.append( !Smb4KSambaOptionsHandler::self()->winsServer().isEmpty() ?
                    " -R -U "+Smb4KSambaOptionsHandler::self()->winsServer()+" " : "" );
    command.append( " -A ip" );
    command.append( " ; " );
  }

  // Get rid of the last 3 characters (" ; "):
  command.truncate( command.length() - 3 );

  m_proc->setShellCommand( command );

  startProcess( IPScan );
}


void Smb4KScanner::scanForWorkgroupMembers( const Smb4KWorkgroup &workgroup )
{
  m_priv->setWorkgroup( workgroup );

  QString command;

  if ( !workgroup.masterBrowserIP().isEmpty() )
  {
    command.append( "net "+Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::ServerDomain ) );
    command.append( " -I "+workgroup.masterBrowserIP() );
    command.append( " -w "+KShell::quoteArg( workgroup.name() ) );
    command.append( " -S "+KShell::quoteArg( workgroup.masterBrowserName() ) );

    if ( Smb4KSettings::masterBrowsersRequireAuth() )
    {
      // Find the master browser and read the authentication information
      // for it.
      Smb4KHost *master = findHost( workgroup.masterBrowserName(), workgroup.name() );

      Smb4KAuthInfo authInfo( master );
      Smb4KWalletManager::self()->readAuthInfo( &authInfo );

      if ( !authInfo.login().isEmpty() )
      {
        command.append( QString( " -U %1" ).arg( KShell::quoteArg( authInfo.login() ) ) );

        if ( !authInfo.password().isEmpty() )
        {
          m_proc->setEnv( "PASSWD", authInfo.password() );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        command.append( " -U %" );
      }
    }
    else
    {
      command.append( " -U %" );
    }
  }
  else
  {
    command.append( "net "+Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::LookupHost, workgroup ) );
    command.append( " -S "+KShell::quoteArg( workgroup.masterBrowserName() ) );
    command.append( " -w "+KShell::quoteArg( workgroup.name() ) );
    command.append( " -U % " );
    // FIXME: Maybe we need to know the shell if the user does not use a
    // sh-compatible one...?
    command.append( "| xargs -Iip " );
    command.append( getenv( "SHELL" ) );
    command.append( " -c 'echo \"*** "+workgroup.masterBrowserName()+": ip ***\" && " );
    command.append( "net "+Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::ServerDomain ) );
    command.append( " -I ip" );
    command.append( " -w "+KShell::quoteArg( workgroup.name() ) );
    command.append( " -S "+KShell::quoteArg( workgroup.masterBrowserName() ) );

    if ( Smb4KSettings::masterBrowsersRequireAuth() )
    {
      Smb4KHost *master = findHost( workgroup.masterBrowserName(), workgroup.name() );

      Smb4KAuthInfo authInfo( master );
      Smb4KWalletManager::self()->readAuthInfo( &authInfo );

      if ( !authInfo.login().isEmpty() )
      {
        command.append( QString( " -U %1'" ).arg( KShell::quoteArg( authInfo.login() ) ) );

        if ( !authInfo.password().isEmpty() )
        {
          m_proc->setEnv( "PASSWD", authInfo.password() );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        command.append( " -U %'" );
      }
    }
    else
    {
      command.append( " -U %'" );
    }
  }

  m_proc->setShellCommand( command );

  startProcess( Hosts );
}


void Smb4KScanner::scanForShares( const Smb4KHost &host )
{
  m_priv->setHost( host );

  Smb4KAuthInfo authInfo( &host );
  Smb4KWalletManager::self()->readAuthInfo( &authInfo );

  QString command;

  command.append( "net "+Smb4KSambaOptionsHandler::self()->netOptions( Smb4KSambaOptionsHandler::Share, host ) );
  command.append( " -w "+KShell::quoteArg( host.workgroup() ) );
  command.append( " -S "+KShell::quoteArg( host.name() ) );

  if ( !host.ip().isEmpty() )
  {
    command.append( QString( " -I %1" ).arg( KShell::quoteArg( host.ip() ) ) );
  }

  if ( !authInfo.login().isEmpty() )
  {
    command.append( QString( " -U %1" ).arg( KShell::quoteArg( authInfo.login() ) ) );

    if ( !authInfo.password().isEmpty() )
    {
      m_proc->setEnv( "PASSWD", authInfo.password() );
    }
  }
  else
  {
    command.append( " -U guest%" );
  }

  m_proc->setShellCommand( command );

  startProcess( Shares );
}


void Smb4KScanner::scanForInfo( const Smb4KHost &host )
{
  m_priv->setHost( host );

  QString smbclient_options = Smb4KSambaOptionsHandler::self()->smbclientOptions();

  QString command;
  command.append( "smbclient -d1 -U guest%" );
  command.append( " -W "+KShell::quoteArg( host.workgroup() ) );
  command.append( " -L "+KShell::quoteArg( host.name() ) );

  if ( !host.ip().isEmpty() )
  {
    command.append( " -I "+KShell::quoteArg( host.ip() ) );
  }

  if ( !smbclient_options.trimmed().isEmpty() )
  {
    command.append( smbclient_options );
  }

  m_proc->setShellCommand( command );

  startProcess( Info );
}


void Smb4KScanner::startProcess( int state )
{
  m_aborted = false;

  m_state = state;

  if ( state != Info )
  {
    QApplication::setOverrideCursor( Qt::WaitCursor );
  }

  m_proc->setOutputChannelMode( KProcess::SeparateChannels );
  m_proc->start();
}


void Smb4KScanner::endProcess( int /*exitCode*/, QProcess::ExitStatus exitStatus )
{
  if ( exitStatus == QProcess::NormalExit )
  {
    switch ( m_state )
    {
      case Workgroups:
      case QueryHost:
      {
        processWorkgroups();
        break;
      }
      case IPScan:
      {
        processIPScan();
        break;
      }
      case Hosts:
      {
        processWorkgroupMembers();
        break;
      }
      case Shares:
      {
        processShares();
        break;
      }
      case Info:
      {
        processInfo( exitStatus );
        break;
      }
      default:
      {
        break;
      }
    }
  }
  else
  {
    // In case we searched for information, invoke processInfo(),
    // since the host needs to be resetted.
    switch ( m_state )
    {
      case Info:
      {
        processInfo( exitStatus );
        break;
      }
      default:
      {
        break;
      }
    }

    // Something went wrong. Throw an error if the problem was not
    // caused by using the abort() function.
    if ( !m_aborted )
    {
      if ( m_process_error != -1 )
      {
        Smb4KCoreMessage::processError( ERROR_PROCESS_ERROR, m_process_error );
      }
      else
      {
        Smb4KCoreMessage::processError( ERROR_PROCESS_EXIT, m_process_error );
      }

      emit failed();
    }
    else
    {
      // Do nothing
    }
  }

  m_state = Idle;

  m_priv->clearData();

  QApplication::restoreOverrideCursor();
  m_proc->clearProgram();

  m_process_error = (QProcess::ProcessError)(-1);
  m_working = false;

  emit state( SCANNER_STOP );
}


void Smb4KScanner::processWorkgroups()
{
  // Read from stderr and decide what to do:
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

  if ( !stderr_output.isEmpty() )
  {
    emit failed();

    Smb4KCoreMessage::error( ERROR_GETTING_WORKGROUPS, QString(), stderr_output );

    // Emit these signals so that the browser can be cleared.
    emit workgroups( *Smb4KGlobal::workgroupsList() );
    emit hostListChanged();

    return;
  }

  // There was no error. We can proceed with processing the
  // output from stdout.
  QString stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();
  QStringList list = stdout_output.split( "\n", QString::KeepEmptyParts );

  QList<Smb4KWorkgroup *> temp_workgroups_list;

  switch ( m_state )
  {
    case Workgroups:
    {
      Smb4KWorkgroup temp_workgroup;
      Smb4KHost temp_host;

      for ( int i = 0; i < list.size(); ++i )
      {
        if ( list.at( i ).startsWith( "Looking up status of" ) )
        {
          // Get the IP address of the host.
          temp_host.setIP( list.at( i ).section( "of", 1, 1 ).trimmed() );

          continue;
        }
        else if ( list.at( i ).contains( "MAC Address", Qt::CaseSensitive ) )
        {
          // Add workgroup and host to global lists.
          if ( !temp_workgroup.masterBrowserName().trimmed().isEmpty() )
          {
            Smb4KWorkgroup *workgroup = findWorkgroup( temp_workgroup.name() );

            if ( workgroup )
            {
              // Check if the master browser changed, get the old one (if necessary)
              // and reset it.
              if ( QString::compare( temp_workgroup.masterBrowserName(),
                   workgroup->masterBrowserName(), Qt::CaseInsensitive ) != 0 )
              {
                Smb4KHost *old_master_browser = findHost( workgroup->masterBrowserName(), workgroup->name() );

                if ( old_master_browser )
                {
                  old_master_browser->setIsMasterBrowser( false );
                }
                else
                {
                  // Do nothing
                }
              }
              else
              {
                // The old master browser is also the new one. The updating will be
                // done below.
              }

              // Update the workgroup.
              workgroup->setMasterBrowser( temp_workgroup.masterBrowserName(),
                                           temp_workgroup.masterBrowserIP(),
                                           false );

              // Put it into the temporary list...
              temp_workgroups_list.append( new Smb4KWorkgroup( *workgroup ) );

              // ... and remove it from the global one.
              int index = Smb4KGlobal::workgroupsList()->indexOf( workgroup );
              delete Smb4KGlobal::workgroupsList()->takeAt( index );
            }
            else
            {
              // Copy temporary workgroup and add it to the temporary list.
              temp_workgroups_list.append( new Smb4KWorkgroup( temp_workgroup ) );
            }
          }
          else
          {
            // Do nothing. The workgroup object has no master browser entry.
          }

          if ( !temp_host.isEmpty() )
          {
            Smb4KHost *host = findHost( temp_host.name(), temp_host.workgroup() );

            if ( host )
            {
              // Update the host.
              host->setIP( temp_host.ip() );
              host->setIsMasterBrowser( temp_host.isMasterBrowser() );
            }
            else
            {
              // Add the host.
              Smb4KGlobal::hostsList()->append( new Smb4KHost( temp_host ) );
            }
          }

          // Clear the temporary objects.
          temp_workgroup = Smb4KWorkgroup();
          temp_host      = Smb4KHost();

          continue;
        }
        else if ( list.at( i ).contains( " <00> ", Qt::CaseSensitive ) )
        {
          // Set the name of the workgroup/host.
          if ( list.at( i ).contains( " <GROUP> ", Qt::CaseSensitive ) )
          {
            temp_workgroup.setName( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
            temp_host.setWorkgroup( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
          }
          else
          {
            temp_host.setName( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
          }

          continue;
        }
        else if ( list.at( i ).contains( "__MSBROWSE__", Qt::CaseSensitive ) &&
                  list.at( i ).contains( " <01> ", Qt::CaseSensitive ) )
        {
          // The host is a master browser.
          temp_workgroup.setMasterBrowser( temp_host.name(), temp_host.ip(), false );
          temp_host.setIsMasterBrowser( true );

          continue;
        }
        else
        {
          continue;
        }
      }

      break;
    }
    case QueryHost:
    {
      Smb4KWorkgroup temp_workgroup;
      Smb4KHost temp_host;

      for ( int i = 0; i < list.size(); ++i )
      {
        if ( list.at( i ).trimmed().startsWith( "Enumerating" ) )
        {
          continue;
        }
        else if ( list.at( i ).trimmed().startsWith( "Domain name" ) )
        {
          continue;
        }
        else if ( list.at( i ).trimmed().startsWith( "-------------" ) )
        {
          continue;
        }
        else if ( list.at( i ).trimmed().isEmpty() )
        {
          continue;
        }
        else
        {
          // This is the workgroup and master entry. Process it.
          temp_workgroup.setName( list.at( i ).section( "   ", 0, 0 ).trimmed() );
          temp_workgroup.setMasterBrowserName( list.at( i ).section( "   ", 1, -1 ).trimmed() );

          temp_host.setName( temp_workgroup.masterBrowserName() );
          temp_host.setWorkgroup( temp_workgroup.name() );
          temp_host.setIsMasterBrowser( true );

          // Get the workgroup if it exists.
          Smb4KWorkgroup *workgroup = findWorkgroup( temp_workgroup.name() );

          if ( workgroup )
          {
            // Check if the master browser changed, get the old one (if necessary)
            // and reset it.
            if ( QString::compare( temp_workgroup.masterBrowserName(),
                 workgroup->masterBrowserName(), Qt::CaseInsensitive ) != 0 )
            {
              Smb4KHost *old_master_browser = findHost( workgroup->masterBrowserName(), workgroup->name() );

              if ( old_master_browser )
              {
                old_master_browser->setIsMasterBrowser( false );
              }
              else
              {
                // Do nothing
              }
            }
            else
            {
              // The old master browser is also the new one. The updating will be
              // done below.
            }

            // Update the workgroup.
            workgroup->setMasterBrowser( temp_workgroup.masterBrowserName(),
                                         QString(),
                                         false );

            // Put it into the temporary list...
            temp_workgroups_list.append( new Smb4KWorkgroup( *workgroup ) );

            // ... and remove it from the global one.
            int index = Smb4KGlobal::workgroupsList()->indexOf( workgroup );
            delete Smb4KGlobal::workgroupsList()->takeAt( index );
          }
          else
          {
            // Copy temporary workgroup and add it to the temporary list.
            temp_workgroups_list.append( new Smb4KWorkgroup( temp_workgroup ) );
          }

          // Search the new master browser in the global hosts list.
          Smb4KHost *host = findHost( temp_host.name(), temp_host.workgroup() );

          if ( host )
          {
            // Update the host.
            host->setIP( temp_host.ip() );
            host->setIsMasterBrowser( temp_host.isMasterBrowser() );
          }
          else
          {
            // Add the host.
            Smb4KGlobal::hostsList()->append( new Smb4KHost( temp_host ) );
          }

          // Clear the temporary objects.
          temp_workgroup = Smb4KWorkgroup();
          temp_host      = Smb4KHost();

          continue;
        }
      }

      break;
    }
    default:
    {
      break;
    }
  }

  // Clean up the internal hosts list. In Smb4KGlobal::workgroupsList()
  // only the obsolete workgroups are present. We use those to remove
  // all obsolete hosts.
  if ( !Smb4KGlobal::workgroupsList()->isEmpty() )
  {
    // Normally the list of obsolete workgroups is rather small.
    // Thus, we will loop through the host list and check the
    // workgroup.
    for ( int i = 0; i < Smb4KGlobal::hostsList()->size(); ++i )
    {
      Smb4KWorkgroup *workgroup = findWorkgroup( Smb4KGlobal::hostsList()->at( i )->workgroup() );

      if ( workgroup )
      {
        // The workgroup is obsolete. Remove the host.
        delete Smb4KGlobal::hostsList()->takeAt( i );

        continue;
      }
      else
      {
        // The workgroup is not obsolete.
        continue;
      }
    }

    // Clear the list of workgroups.
    while ( !Smb4KGlobal::workgroupsList()->isEmpty() )
    {
      delete Smb4KGlobal::workgroupsList()->takeFirst();
    }
  }
  else
  {
    // Do nothing
  }

  // Update the list of workgroups. We can append the new
  // list, because Smb4KGlobal::workgroupsList() is empty.
  *Smb4KGlobal::workgroupsList() += temp_workgroups_list;

  Smb4KIPAddressScanner::self()->triggerScan();

  emit workgroups( *Smb4KGlobal::workgroupsList() );
  emit hostListChanged();
}


void Smb4KScanner::processIPScan()
{
  // Read from stderr and decide what to do:
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

  if ( !stderr_output.isEmpty() )
  {
    emit failed();

    Smb4KCoreMessage::error( ERROR_PERFORMING_IPSCAN, QString(), stderr_output );

    // Emit these signals so that the browser can be cleared.
    emit workgroups( *Smb4KGlobal::workgroupsList() );
    emit hostListChanged();

    return;
  }

  // There was no error. We can proceed with processing the
  // output from stdout.
  QString stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();
  QStringList list = stdout_output.split( "\n", QString::KeepEmptyParts );

  QList<Smb4KWorkgroup *> temp_workgroups_list;
  QList<Smb4KHost *> temp_hosts_list;

  // Process the data:
  Smb4KWorkgroup temp_workgroup;
  Smb4KHost temp_host;

  for ( int i = 0; i < list.size(); ++i )
  {
    if ( list.at( i ).startsWith( "Looking up status of" ) )
    {
      // Get the IP address of the host.
      temp_host.setIP( list.at( i ).section( "of", 1, 1 ).trimmed() );

      continue;
    }
    else if ( list.at( i ).contains( "MAC Address", Qt::CaseSensitive ) )
    {
      // Add workgroup and host to global lists.
      if ( !temp_workgroup.masterBrowserName().trimmed().isEmpty() )
      {
        Smb4KWorkgroup *workgroup = findWorkgroup( temp_workgroup.name() );

        if ( workgroup )
        {
          // Find out if we need to add it to the temporary list.
          bool found_workgroup = false;

          for ( int j = 0; j < temp_workgroups_list.size(); ++j )
          {
            if ( QString::compare( temp_workgroups_list.at( j )->name(), workgroup->name() ) == 0 )
            {
              found_workgroup = true;

              break;
            }
            else
            {
              continue;
            }
          }

          if ( !found_workgroup )
          {
            // Update the workgroup.
            workgroup->setMasterBrowser( temp_workgroup.masterBrowserName(),
                                         temp_workgroup.masterBrowserIP(),
                                         false );

            temp_workgroups_list.append( new Smb4KWorkgroup( *workgroup ) );
          }
          else
          {
            // Do nothing
          }
        }
        else
        {
          // Copy temporary workgroup and add it to the temporary list.
          temp_workgroups_list.append( new Smb4KWorkgroup( temp_workgroup ) );
        }
      }
      else
      {
        // Do nothing. The workgroup object has no master browser entry.
      }

      if ( !temp_host.isEmpty() )
      {
        Smb4KHost *host = findHost( temp_host.name(), temp_host.workgroup() );

        if ( host )
        {
          // Update the host.
          host->setIP( temp_host.ip() );
          host->setIsMasterBrowser( temp_host.isMasterBrowser() );

          temp_hosts_list.append( new Smb4KHost( *host ) );
        }
        else
        {
          temp_hosts_list.append( new Smb4KHost( temp_host ) );
        }
      }

      // Clear the temporary objects.
      temp_workgroup = Smb4KWorkgroup();
      temp_host      = Smb4KHost();

      continue;
    }
    else if ( list.at( i ).contains( " <00> ", Qt::CaseSensitive ) )
    {
      // Set the name of the workgroup/host.
      if ( list.at( i ).contains( " <GROUP> ", Qt::CaseSensitive ) )
      {
        temp_workgroup.setName( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
        temp_host.setWorkgroup( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
      }
      else
      {
        temp_host.setName( list.at( i ).section( "<00>", 0, 0 ).trimmed() );
      }

      continue;
    }
    else if ( list.at( i ).contains( "__MSBROWSE__", Qt::CaseSensitive ) &&
              list.at( i ).contains( " <01> ", Qt::CaseSensitive ) )
    {
      // The host is a master browser.
      temp_workgroup.setMasterBrowser( temp_host.name(), temp_host.ip(), false );
      temp_host.setIsMasterBrowser( true );

      continue;
    }
    else
    {
      continue;
    }
  }

  // No extra lookup of IP addresses is needed since
  // we searched for IP addresses.

  // Clear the internal list of workgroups and replace
  // it with the temporary one.
  while ( !Smb4KGlobal::workgroupsList()->isEmpty() )
  {
    delete Smb4KGlobal::workgroupsList()->takeFirst();
  }

  *Smb4KGlobal::workgroupsList() += temp_workgroups_list;

  // Clear the internal list of hosts and replace it with
  // the temporary one.
  while ( !Smb4KGlobal::hostsList()->isEmpty() )
  {
    delete Smb4KGlobal::hostsList()->takeFirst();
  }

  *Smb4KGlobal::hostsList() += temp_hosts_list;

  emit workgroups( *Smb4KGlobal::workgroupsList() );
  emit hostListChanged();
}


void Smb4KScanner::processWorkgroupMembers()
{
  // Read from stderr and decide what to do:
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

  if ( !stderr_output.isEmpty() )
  {
    if ( Smb4KSettings::lookupDomains() ||
         Smb4KSettings::queryCurrentMaster() ||
         Smb4KSettings::queryCustomMaster() )
    {
      if ( stderr_output.contains( "The username or password was not correct." ) ||
           stderr_output.contains( "NT_STATUS_ACCOUNT_DISABLED" ) /* AD error */ ||
           stderr_output.contains( "NT_STATUS_ACCESS_DENIED" ) ||
           stderr_output.contains( "NT_STATUS_LOGON_FAILURE" ) )
      {
        // Authentication failed.
        emit failed();

        Smb4KAuthInfo authInfo( &m_priv->host() );

        if ( Smb4KWalletManager::self()->showPasswordDialog( &authInfo ) )
        {
          m_queue.enqueue( QueueContainer( Hosts, m_priv->host() ) );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        // Notify the rest of the program, that something went wrong.
        emit failed();

        // Notify the user.
        Smb4KCoreMessage::error( ERROR_GETTING_MEMBERS, QString(), stderr_output );
      }
    }
    else if ( Smb4KSettings::scanBroadcastAreas() )
    {
      // We are in IP scan mode, so we can ignore the error and emit
      // what we already have.
      emit members( m_priv->workgroup().name(), *Smb4KGlobal::hostsList() );
      emit hostListChanged();
    }
    else
    {
      // Do nothing
    }

    return;
  }

  // There was no error. We can proceed with processing the
  // output from stdout.
  QString stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();
  QStringList list = stdout_output.split( "\n", QString::SkipEmptyParts );

  if ( Smb4KSettings::lookupDomains() ||
       Smb4KSettings::queryCurrentMaster() ||
       Smb4KSettings::queryCustomMaster() )
  {
    QList<Smb4KHost *> temp_hosts_list;
    Smb4KHost temp_host;

    for ( int i = 0; i < list.size(); ++i )
    {
      if ( list.at( i ).startsWith( "***" ) && list.at( i ).endsWith( "***" ) )
      {
        // This is the IP address of the master browser. Add it
        // to the respective workgroup, if necessary.
        Smb4KWorkgroup *workgroup = findWorkgroup( m_priv->workgroup().name() );

        if ( workgroup && workgroup->masterBrowserIP().isEmpty() )
        {
          workgroup->setMasterBrowserIP( list.at( i ).section( ":", 1, 1 ).section( "***", 0, 0 ).trimmed() );
        }
        else
        {
          // Do nothing
        }

        continue;
      }
      else if ( list.at( i ).trimmed().startsWith( "Enumerating" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().startsWith( "Server name" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().startsWith( "-------------" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().isEmpty() )
      {
        continue;
      }
      else
      {
        temp_host.setName( list.at( i ).section( "   ", 0, 0 ).trimmed() );
        temp_host.setWorkgroup( m_priv->workgroup().name() );
        temp_host.setComment( list.at( i ).section( "   ", 1, -1 ).trimmed() );

        // Check if the host is equal to the master browser of the
        // workgroup. If it is, copy the IP address and set the master
        // browser flag to true.
        Smb4KWorkgroup *workgroup = findWorkgroup( temp_host.workgroup() );

        if ( workgroup && QString::compare( temp_host.name(), workgroup->masterBrowserName(), Qt::CaseInsensitive ) == 0 )
        {
          if ( temp_host.ip().isEmpty() && !workgroup->masterBrowserIP().isEmpty() )
          {
            temp_host.setIP( workgroup->masterBrowserIP() );
          }
          else
          {
            // Do nothing
          }

          temp_host.setIsMasterBrowser( true );
        }
        else
        {
          // Do nothing
        }

        // Search the host in the global list. If it already exists, update it,
        // copy it to the temporary list and delete it from the global list
        // (it will be reentered in a short moment). If it does not exist yet,
        // just copy the temporary host to the list.
        Smb4KHost *host = NULL;

        if ( (host = findHost( temp_host.name(), temp_host.workgroup() )) != NULL )
        {
          host->setComment( temp_host.comment() );

          temp_hosts_list.append( new Smb4KHost( *host ) );

          int index = Smb4KGlobal::hostsList()->indexOf( host );
          delete Smb4KGlobal::hostsList()->takeAt( index );
        }
        else
        {
          temp_hosts_list.append( new Smb4KHost( temp_host ) );
        }

        temp_host = Smb4KHost();

        continue;
      }
    }

    // If the temporary list should be empty, put at least the master
    // browser in.
    if ( temp_hosts_list.isEmpty() )
    {
      temp_host.setName( m_priv->workgroup().masterBrowserName() );
      temp_host.setWorkgroup( m_priv->workgroup().name() );
      temp_host.setIP( m_priv->workgroup().masterBrowserIP() );
      temp_host.setIsMasterBrowser( true );

      temp_hosts_list.append( new Smb4KHost( temp_host ) );
    }
    else
    {
      // Do nothing
    }

    // Add the temporary hosts list to the global one.
    *Smb4KGlobal::hostsList() += temp_hosts_list;

    // Emit the signal that the workgroup members have been
    // collected.
    emit members( m_priv->workgroup().name(), temp_hosts_list );

    // Lookup missing IP addresses
    Smb4KIPAddressScanner::self()->triggerScan();
  }
  else if ( Smb4KSettings::scanBroadcastAreas() )
  {
    // We will not remove any host from the list in IP scan mode,
    // but we will add additional infomation, if available.
    for ( int i = 0; i < list.size(); ++i )
    {
      if ( list.at( i ).trimmed().startsWith( "Enumerating" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().startsWith( "Server name" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().startsWith( "-------------" ) )
      {
        continue;
      }
      else if ( list.at( i ).trimmed().isEmpty() )
      {
        continue;
      }
      else
      {
        // FIXME: Should we add newly discovered hosts to the global list?

        // Look for a comment and add it to the host item
        // if one was found.
        if ( !list.at( i ).trimmed().contains( "   " ) )
        {
          // No comment.
          continue;
        }
        else
        {
          Smb4KHost *host = findHost( list.at( i ).section( "   ", 0, 0 ).trimmed(),
                            m_priv->workgroup().name() );

          if ( host )
          {
            host->setComment( list.at( i ).section( "   ", 1, -1 ).trimmed() );
          }
          else
          {
            // Do nothing.
          }
        }
      }
    }

    emit members( m_priv->workgroup().name(), *Smb4KGlobal::hostsList() );
  }
  else
  {
    // Do nothing
  }

  emit hostListChanged();
}


void Smb4KScanner::processShares()
{
  // Read from stderr and decide what to do:
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

  if ( !stderr_output.isEmpty() )
  {
    if ( stderr_output.contains( "The username or password was not correct." ) ||
         stderr_output.contains( "NT_STATUS_ACCOUNT_DISABLED" ) /* AD error */ ||
         stderr_output.contains( "NT_STATUS_ACCESS_DENIED" ) ||
         stderr_output.contains( "NT_STATUS_LOGON_FAILURE" ) )
    {
      // Authentication failed.
      emit failed();

      Smb4KAuthInfo authInfo( &m_priv->host() );

      if ( Smb4KWalletManager::self()->showPasswordDialog( &authInfo ) )
      {
        m_queue.enqueue( QueueContainer( Shares, m_priv->host() ) );
      }
      else
      {
        // Do nothing
      }
    }
    else if ( stderr_output.contains( "could not obtain sid for domain", Qt::CaseSensitive ) )
    {
      // FIXME: Does this error only occur when we scan a server that is
      // only capable of the RAP protocol or also under other conditions?
      m_priv->host().setProtocol( Smb4KHost::RAP );

      m_queue.enqueue( QueueContainer( Shares, m_priv->host() ) );

      m_priv->retry = true;
    }
    else
    {
      // We could not get the list of shares:
      emit failed();

      // Notify the user:
      Smb4KCoreMessage::error( ERROR_GETTING_SHARES, QString(), stderr_output );
    }

    return;
  }

  // There was no error. We can proceed with processing the
  // output from stdout.
  QString stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();
  QStringList list = stdout_output.split( "\n", QString::SkipEmptyParts );

  QList<Smb4KShare *> temp_shares_list;
  Smb4KShare temp_share;

  for ( int i = 0; i < list.size(); ++i )
  {
    if ( list.at( i ).trimmed().startsWith( "Enumerating" ) )
    {
      continue;
    }
    else if ( list.at( i ).trimmed().startsWith( "Share name" ) )
    {
      continue;
    }
    else if ( list.at( i ).trimmed().startsWith( "----------" ) )
    {
      continue;
    }
    else if ( list.at( i ).trimmed().isEmpty() )
    {
      continue;
    }
    else
    {
      if ( list.at( i ).contains( " Disk     ", Qt::CaseSensitive ) )
      {
        temp_share.setName( list.at( i ).section( " Disk     ", 0, 0 ).trimmed() );
        temp_share.setHost( m_priv->host().name() );
        temp_share.setWorkgroup( m_priv->host().workgroup() );
        temp_share.setType( "Disk" );
        temp_share.setComment( list.at( i ).section( " Disk     ", 1, 1 ).trimmed() );

        // Check if we also have an IP address we can add to the
        // share object.
        if ( m_priv->host().ipChecked() && !m_priv->host().ip().isEmpty() )
        {
          temp_share.setHostIP( m_priv->host().ip() );
        }
        else
        {
          Smb4KHost *host = findHost( m_priv->host().name(), m_priv->host().workgroup() );

          if ( host && host->ipChecked() && !host->ip().isEmpty() )
          {
            temp_share.setHostIP( host->ip() );
          }
          else
          {
            // Do nothing
          }
        }

        // In case this is a 'homes' share, set also the user names.
        if ( QString::compare( temp_share.name(), "homes" ) == 0 )
        {
          Smb4KHomesSharesHandler::self()->setHomesUsers( &temp_share );
        }
        else
        {
          // Do nothing
        }

        temp_shares_list.append( new Smb4KShare( temp_share ) );

        temp_share = Smb4KShare();

        continue;
      }
      else if ( list.at( i ).contains( " IPC      ", Qt::CaseSensitive ) )
      {
        temp_share.setName( list.at( i ).section( " IPC      ", 0, 0 ).trimmed() );
        temp_share.setHost( m_priv->host().name() );
        temp_share.setWorkgroup( m_priv->host().workgroup() );
        temp_share.setType( "IPC" );
        temp_share.setComment( list.at( i ).section( " IPC      ", 1, 1 ).trimmed() );

        // Check if we also have an IP address we can add to the
        // share object.
        if ( m_priv->host().ipChecked() && !m_priv->host().ip().isEmpty() )
        {
          temp_share.setHostIP( m_priv->host().ip() );
        }
        else
        {
          Smb4KHost *host = findHost( m_priv->host().name(), m_priv->host().workgroup() );

          if ( host && host->ipChecked() && !host->ip().isEmpty() )
          {
            temp_share.setHostIP( host->ip() );
          }
          else
          {
            // Do nothing
          }
        }

        temp_shares_list.append( new Smb4KShare( temp_share ) );

        temp_share = Smb4KShare();

        continue;
      }
      else if ( list.at( i ).contains( " Print    ", Qt::CaseSensitive ) )
      {
        temp_share.setName( list.at( i ).section( " Print    ", 0, 0 ).trimmed() );
        temp_share.setHost( m_priv->host().name() );
        temp_share.setWorkgroup( m_priv->host().workgroup() );
        temp_share.setType( "Printer" );
        temp_share.setComment( list.at( i ).section( " Print    ", 1, 1 ).trimmed() );

        // Check if we also have an IP address we can add to the
        // share object.
        if ( m_priv->host().ipChecked() && !m_priv->host().ip().isEmpty() )
        {
          temp_share.setHostIP( m_priv->host().ip() );
        }
        else
        {
          Smb4KHost *host = findHost( m_priv->host().name(), m_priv->host().workgroup() );

          if ( host && host->ipChecked() && !host->ip().isEmpty() )
          {
            temp_share.setHostIP( host->ip() );
          }
          else
          {
            // Do nothing
          }
        }

        // Printer shares cannot be mounted, thus we do not need to check
        // here if it is.

        temp_shares_list.append( new Smb4KShare( temp_share ) );

        temp_share = Smb4KShare();

        continue;
      }
      else
      {
        continue;
      }
    }

    continue;
  }

  // Check if a share in the list is mounted.
  for ( int i = 0; i < temp_shares_list.size(); ++i )
  {
    for ( int j = 0; j < Smb4KGlobal::mountedSharesList()->size(); ++j )
    {
      if ( QString::compare( Smb4KGlobal::mountedSharesList()->at( j )->host(), temp_shares_list.at( i )->host() ) == 0 &&
           QString::compare( Smb4KGlobal::mountedSharesList()->at( j )->name(), temp_shares_list.at( i )->name() ) == 0 )
      {
        temp_shares_list.at( i )->setMountData( Smb4KGlobal::mountedSharesList()->at( j ) );

        break;
      }
      else
      {
        continue;
      }
    }
  }

  emit shares( m_priv->host().name(), temp_shares_list );
}


void Smb4KScanner::processInfo( QProcess::ExitStatus status )
{
  switch ( status )
  {
    case QProcess::NormalExit:
    {
      // Since the information about the server and operating system
      // is debug information, it will be reported via stderr.
      QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

      // Get the host.
      Smb4KHost *host = findHost( m_priv->host().name(), m_priv->host().workgroup() );

      if ( host )
      {
        // The output should only be one line, so we can process it
        // immediately.
        if ( stderr_output.trimmed().startsWith( "Domain" ) || stderr_output.trimmed().startsWith( "OS" ) )
        {
          host->setInfo( stderr_output.section( "Server=[", 1, 1 ).section( "]", 0, 0 ).trimmed(),
                         stderr_output.section( "OS=[", 1, 1 ).section( "]", 0, 0 ).trimmed() );
        }
        else
        {
          emit failed();
        }

        emit info( host );
      }
      else
      {
        // Do nothing
      }

      break;
    }
    default:
    {
      // Enable checking again.
      Smb4KHost *host = findHost( m_priv->host().name(), m_priv->host().workgroup() );

      if ( host )
      {
        host->resetInfo();
      }
      else
      {
        // Do nothing
      }
    }
  }
}


void Smb4KScanner::insertHost( Smb4KHost *host )
{
  if ( host && !findHost( host->name(), host->workgroup() ) )
  {
    // Use the copy constructor here, so that we do not run into
    // trouble when/if host is deleted.
    Smb4KHost *new_host = new Smb4KHost( *host );

    Smb4KGlobal::hostsList()->append( new_host );

    // Check if the workgroup is already known. If not, create a new Smb4KWorkgroup object,
    // declare the host a pseudo master and add the workgroup to the list.
    if ( !findWorkgroup( new_host->workgroup() ) )
    {
      Smb4KWorkgroup *workgroup = new Smb4KWorkgroup( new_host->workgroup() );
      workgroup->setMasterBrowser( new_host->name(), new_host->ip(), true );

      new_host->setIsMasterBrowser( true );  // pseudo master

      appendWorkgroup( workgroup );
    }

    // Lookup at least the IP address of this host, if necessary:
    if ( new_host->ip().isEmpty() )
    {
      Smb4KIPAddressScanner::self()->triggerScan();
    }

    emit hostInserted( new_host );
    emit hostListChanged();
  }
}


void Smb4KScanner::appendWorkgroup( Smb4KWorkgroup *workgroup )
{
  if ( !findWorkgroup( workgroup->name() ) )
  {
    // Append the workgroup.
    Smb4KGlobal::workgroupsList()->append( workgroup );

    // Add the master browser to the list of hosts.
    if ( !workgroup->masterBrowserName().isEmpty() )
    {
      Smb4KHost *master_browser = new Smb4KHost( workgroup->masterBrowserName() );
      master_browser->setWorkgroup( workgroup->name() );
      master_browser->setIP( workgroup->masterBrowserIP() );
    }

    emit workgroups( *Smb4KGlobal::workgroupsList() );
    emit hostListChanged();
  }
  else
  {
    // Do nothing
  }
}


void Smb4KScanner::timerEvent( QTimerEvent * )
{
  if ( !m_working && !m_queue.isEmpty() )
  {
    // Tell the program, that the scanner is running.
    m_working = true;

    QueueContainer c = m_queue.dequeue();

    switch ( c.todo() )
    {
      case Init:
      {
        if ( Smb4KSettings::lookupDomains() )
        {
          emit state( SCANNER_LOOKUP_DOMAINS );
          lookupDomains();
        }
        else if ( Smb4KSettings::queryCurrentMaster() ||
                  Smb4KSettings::queryCustomMaster() )
        {
          emit state( SCANNER_QUERY_MASTER_BROWSER );
          queryMasterBrowser();
        }
        else if ( Smb4KSettings::scanBroadcastAreas() )
        {
          emit state( SCANNER_SCAN_BROADCAST_AREAS );
          scanBroadcastAreas();
        }
        else
        {
          // Do nothing
        }

        break;
      }
      case Hosts:
      {
        emit state( SCANNER_OPEN_WORKGROUP );
        scanForWorkgroupMembers( c.workgroup() );
        break;
      }
      case Shares:
      {
        emit state( SCANNER_OPEN_HOST );

        if ( m_priv->retry )
        {
          m_priv->retry = false;
        }

        scanForShares( c.host() );
        break;
      }
      case Info:
      {
        emit state( SCANNER_QUERY_INFO );
        scanForInfo( c.host() );
        break;
      }
      default:
      {
        break;
      }
    }
  }
  else
  {
    // Do nothing
  }
}


/////////////////////////////////////////////////////////////////////////////
// SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////


void Smb4KScanner::slotProcessFinished( int exitCode, QProcess::ExitStatus exitStatus )
{
  endProcess( exitCode, exitStatus );
}


void Smb4KScanner::slotProcessError( QProcess::ProcessError errorCode )
{
  m_process_error = errorCode;
}


#include <smb4kscanner.moc>
