Logo Search packages:      
Sourcecode: kcmpureftpd version File versions  Download package

kcmpureftpdstat.cpp

/***************************************************************************
                          kcmpureftpdstat.cpp  -  PureFTPd Statistics
                             -------------------
    begin                : Thu Jan 2 2002
    copyright            : (C) 2002,2003 by Claudiu Costin
    email                : claudiuc@kde.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.                                   *
 *                                                                         *
 ***************************************************************************/


#include <qclipboard.h>
#include <qwhatsthis.h>
#include <qcstring.h>
#include <qstringlist.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qtextstream.h>
#include <qdom.h>
#include <qregexp.h>
#include <qlayout.h>
#include <qbuttongroup.h>

#include <klocale.h>
#include <kcursor.h>
#include <kseparator.h>
#include <kstddirs.h>
#include <kmessagebox.h>
#include <kfiledialog.h>
#include <kiconloader.h>
#include <kapplication.h>

#include "kcmpureftpdstat.moc"

KPureftpdStat::KPureftpdStat(QWidget *parent, const char *name)
        : KCModule(parent, name), infoProcess(0), logProcess(0)  {

  pureftpwhoExe=QString::null;
  logMaxNumLines=0;
  reachedMaxNumLines=false;
  deliberatedKill=false;
  isProcessingInfo=false;
  mFound=false;

  config = new KConfig("kcmpureftpdstatrc",false,false);
  infoTimer = new QTimer(this);
  connect(infoTimer,SIGNAL(timeout()),this,SLOT(startInfoProcess()));
    
  initView();
    

  // ensure changed() when user modify settings
  connect(rbInfoDefault,SIGNAL(toggled(bool)),this,SLOT(configChanged()));
  connect(rbInfoCustom,SIGNAL(toggled(bool)),this,SLOT(configChanged()));
  connect(rbLogFile,SIGNAL(toggled(bool)),this,SLOT(configChanged()));
  connect(rbLogCustom,SIGNAL(toggled(bool)),this,SLOT(configChanged()));
  connect(pureftpwhoDefault,SIGNAL(toggled(bool)),this,SLOT(configChanged()));
  connect(pureftpwhoPath,SIGNAL(textChanged(const QString &)),this,SLOT(configChanged(const QString &)));
  connect(logFileName,SIGNAL(textChanged(const QString &)),this,SLOT(configChanged(const QString &)));
  connect(infoCommand,SIGNAL(textChanged(const QString &)),this,SLOT(configChanged(const QString &)));
  connect(logCommand,SIGNAL(textChanged(const QString &)),this,SLOT(configChanged(const QString &)));
  connect(updateInterval,SIGNAL(valueChanged(int)),this,SLOT(configChanged(int)));
  connect(historyLines,SIGNAL(valueChanged(int)),this,SLOT(configChanged(int)));

  load();

  displayInfoMessage(InfoInit);
  displayLogMessage(LogInit);

  logProcess = new KShellProcess;
  connect(logProcess,SIGNAL(receivedStdout(KProcess *,char*, int)),this,SLOT(slotLogProcessRecvStdout(KProcess *,char*, int)));
  connect(logProcess,SIGNAL(receivedStderr(KProcess *,char*, int)),this,SLOT(slotLogProcessRecvStderr(KProcess *,char*, int)));
  connect(logProcess,SIGNAL(processExited(KProcess *)),this,SLOT(slotLogProcessExited(KProcess *)));

  infoProcess = new KShellProcess;
  connect(infoProcess,SIGNAL(receivedStdout(KProcess *,char *, int)),this,SLOT(slotInfoProcessRecvStdout(KProcess *, char*, int)));
  connect(infoProcess,SIGNAL(receivedStderr(KProcess *,char *, int)),this,SLOT(slotInfoProcessRecvStderr(KProcess *, char*, int)));
  connect(infoProcess,SIGNAL(processExited(KProcess *)),this,SLOT(slotInfoProcessExited(KProcess *)));
}


KPureftpdStat::~KPureftpdStat() {
  if (infoTimer->isActive()) {
    infoTimer->stop();
  }
  if (infoProcess->isRunning()) {
    stopInfoProcess();
  }
  if (logProcess->isRunning()) {
    stopLogProcess();
  }
}

void KPureftpdStat::initView() {
    
  QVBoxLayout *vbmain = new QVBoxLayout(this,0,0);
  tab = new QTabWidget(this);
  vbmain->addWidget(tab);

  // "Information" tab
  tabInfo = new QWidget(this);
  tab->addTab(tabInfo,i18n("Information"));
  QWhatsThis::add(tabInfo, i18n(
                      "<qml>Informations about current connections to your "
                      "<b>pure-ftpd</b> FTP server</qml>"
                  ));
  QVBoxLayout *vbox1 = new QVBoxLayout(tabInfo,5,5);
  QGroupBox *grpConn = new QGroupBox(i18n("FTP Connections"),tabInfo);
  QVBoxLayout *vbox11 = new QVBoxLayout(grpConn,5,5);
  vbox11->addSpacing(grpConn->fontMetrics().height());
  connList = new KListView(grpConn);

  connList->addColumn(i18n("Username"),-1);
  connList->addColumn(i18n("State"),-1);
  connList->addColumn(i18n("Time"),-1);
  connList->addColumn(i18n("Percentage"),-1);
  connList->addColumn(i18n("File"),-1);
  connList->setMaximumHeight(10*connList->fontMetrics().height());
  connList->setAllColumnsShowFocus(true);
  connList->setShowSortIndicator(true);
  connList->setColumnAlignment(2,Qt::AlignRight);
  connList->setColumnAlignment(3,Qt::AlignRight);
  vbox11->addWidget(connList);

  QGroupBox *grpConnInfo = new QGroupBox(i18n("Extended Informations"),tabInfo);
  QVBoxLayout *vbox12 = new QVBoxLayout(grpConnInfo,5,5);
  vbox12->addSpacing(grpConnInfo->fontMetrics().height());
  txtExtInfo = new QTextView(grpConnInfo);
  vbox12->addWidget(txtExtInfo);

  infoUpdate = new KPushButton(i18n("Continuos &Update"),tabInfo);
  infoUpdate->setToggleButton(true);

  grpInfoMessage = new QGroupBox(i18n(" Message "),tabInfo);
  grpInfoMessage->setMinimumHeight(8*grpInfoMessage->fontMetrics().height());
  QGridLayout *gbox13 = new QGridLayout(grpInfoMessage,2,2,5);
  mesgInfoIcon = new QLabel(grpInfoMessage);
  mesgInfoIcon->setMargin(10);
  mesgInfoText = new QLabel(grpInfoMessage);
  mesgInfoText->setAlignment(Qt::AlignLeft | Qt::AlignTop);

  gbox13->addRowSpacing(0,grpInfoMessage->fontMetrics().height());
  gbox13->setColStretch(1,10);
  gbox13->setRowStretch(1,10);
  gbox13->addWidget(mesgInfoIcon,1,0,Qt::AlignCenter);
  gbox13->addWidget(mesgInfoText,1,1);

  vbox1->addWidget(grpConn);
  vbox1->addSpacing(5);
  vbox1->addWidget(grpConnInfo);
  vbox1->addSpacing(5);
  vbox1->addWidget(infoUpdate,0,Qt::AlignRight);
  vbox1->addSpacing(5);
  vbox1->addWidget(grpInfoMessage);
  vbox1->addStretch();

  connect(infoUpdate,SIGNAL(clicked()),this,SLOT(slotInfoProcessToggleUpdate()));
  connect(connList,SIGNAL(clicked(QListViewItem*)),this,SLOT(slotShowExtendedInfo(QListViewItem*)));


  // "Log" tab
  tabLog = new QWidget(this);
  tab->addTab(tabLog,i18n("Log"));
  QWhatsThis::add(tabLog, i18n(
                      "<qml>Show <b>pure-ftpd</b> server log file activity "
                      "in real time. Also you can search in log view "
                      "for some special keywords using usual search "
                      "options.</qml>"
                  ));
  QVBoxLayout *vbox2 = new QVBoxLayout(tabLog,5,5);
  QLabel *txtLogOutput = new QLabel(i18n("Log Output:"),tabLog);
  logOutput = new QMultiLineEdit(tabLog);
  logOutput->setReadOnly(true);
  logOutput->setMinimumHeight(15*logOutput->fontMetrics().height());
  logOutput->setFont(KGlobalSettings::fixedFont());
  logOutput->setWordWrap(QTextEdit::NoWrap);
  KCursor::setAutoHideCursor(logOutput,true);

  logUpdate = new KPushButton(i18n("Continuos &Update"),tabLog);
  logUpdate->setToggleButton(true);
  logSave   = new KPushButton(i18n("&Save..."),tabLog);
  logCopy   = new KPushButton(i18n("&Copy to Clipboard"),tabLog);
  logClear  = new KPushButton(i18n("Cl&ear"),tabLog);

  QHBoxLayout *hbox21 = new QHBoxLayout();
  hbox21->addWidget(logSave);
  hbox21->addSpacing(5);
  hbox21->addWidget(logCopy);
  hbox21->addSpacing(5);
  hbox21->addWidget(logClear);
  hbox21->addStretch();
  hbox21->addWidget(logUpdate);


  KSeparator *sep = new KSeparator(KSeparator::HLine,tabLog);

  caseSensitive = new QCheckBox(i18n("Case &sensitive"),tabLog);
  wholeWords = new QCheckBox(i18n("&Whole words only"),tabLog);
  findBackwards = new QCheckBox(i18n("Find &backwards"),tabLog);
  logSearch = new KPushButton(i18n("&Search"),tabLog);
  logResetSearch = new KPushButton(i18n("&Reset Search"),tabLog);
  QLabel *txtSearch = new QLabel(i18n("Sea&rch for:"),tabLog);
  editSearch = new KLineEdit(tabLog);
  txtSearch->setBuddy(editSearch);

  QGridLayout *gbox22 = new QGridLayout();
  gbox22->addWidget(txtSearch,1,1,Qt::AlignRight);
  gbox22->addWidget(editSearch,1,2);
  gbox22->addWidget(logSearch,1,3,Qt::AlignLeft);
  gbox22->addWidget(logResetSearch,2,3,Qt::AlignLeft);
  gbox22->addMultiCellWidget(caseSensitive,2,2,1,2);
  gbox22->addWidget(logResetSearch,2,3,Qt::AlignRight);
  gbox22->addMultiCellWidget(wholeWords,3,3,1,2);
  gbox22->addMultiCellWidget(findBackwards,4,4,1,2);

  grpLogMessage = new QGroupBox(i18n(" Message "),tabLog);
  grpLogMessage->setMinimumHeight(8*grpLogMessage->fontMetrics().height());
  QGridLayout *gbox23 = new QGridLayout(grpLogMessage,2,2,5);
  mesgLogIcon = new QLabel(grpLogMessage);
  mesgLogIcon->setMargin(10);
  mesgLogText = new QLabel(grpLogMessage);
  mesgLogText->setAlignment(Qt::AlignLeft | Qt::AlignTop);

  gbox23->addRowSpacing(0,grpLogMessage->fontMetrics().height());
  gbox23->setColStretch(1,10);
  gbox23->setRowStretch(1,10);
  gbox23->addWidget(mesgLogIcon,1,0,Qt::AlignCenter);
  gbox23->addWidget(mesgLogText,1,1);


  vbox2->addWidget(txtLogOutput);
  vbox2->addWidget(logOutput);
  vbox2->addSpacing(5);
  vbox2->addLayout(hbox21);
  vbox2->addSpacing(5);
  vbox2->addWidget(grpLogMessage);
  vbox2->addSpacing(5);
  vbox2->addWidget(sep);
  vbox2->addSpacing(5);
  vbox2->addLayout(gbox22);
  vbox2->addStretch();

  connect(logUpdate,SIGNAL(toggled(bool)),this,SLOT(slotLogProcessToggleUpdate(bool)));
  connect(logSave,SIGNAL(clicked()),this,SLOT(slotLogSave()));
  connect(logCopy,SIGNAL(clicked()),this,SLOT(slotLogCopy()));
  connect(logClear,SIGNAL(clicked()),this,SLOT(slotLogClear()));
  connect(logSearch,SIGNAL(clicked()),this,SLOT(slotLogSearch()));
  connect(logResetSearch,SIGNAL(clicked()),this,SLOT(slotLogResetSearch()));



  // "Configuration" tab
  tabConfig = new QWidget(this);
  tab->addTab(tabConfig,i18n("Configuration"));
  QWhatsThis::add(tabConfig, i18n(
                      "<qml>Here you can configure parameteres for "
                      "Pureftpd Statistics KDE module. You may want to "
                      "modify default values if you have unsual configuration "
                      "or your machine is powerfull enough.</qml>"
                  ));
  QVBoxLayout *vbox3 = new QVBoxLayout(tabConfig,5,5);
  QButtonGroup *grpInfoConf = new QButtonGroup(i18n(" FTP Sessions Display Settings "),tabConfig);
  QGridLayout *gbox31 = new QGridLayout(grpInfoConf,7,3,5);

  QLabel *txtUpdateInterval = new QLabel(i18n("&Update interval:"),grpInfoConf);
  updateInterval = new KIntNumInput(grpInfoConf);
  updateInterval->setRange(1,10000,1,false);
  updateInterval->setSuffix(i18n("seconds"," sec"));
  txtUpdateInterval->setBuddy(updateInterval);
  QLabel *txtInfoChoose = new QLabel(i18n("Choose information source:"),grpInfoConf);
  rbInfoDefault = new QRadioButton(i18n("Run pure-ftp&who"),grpInfoConf);
  pureftpwhoDefault = new QCheckBox(i18n("Use s&etting from \"PureFTPd Configuration\" module"), grpInfoConf);
  txtPureftpwhoPath = new QLabel(i18n("Pure-ftpwho Path:"),grpInfoConf);
  pureftpwhoPath = new KURLRequester("",grpInfoConf);
  txtPureftpwhoPath->setBuddy(pureftpwhoPath);
#if KDE_VERSION > 300
  pureftpwhoPath->setMode(KFile::File);
#endif
  rbInfoCustom = new QRadioButton(i18n("&Run custom command"),grpInfoConf);
  txtInfoCommand = new QLabel(i18n("Command Line:"),grpInfoConf);
  infoCommand = new KLineEdit(grpInfoConf);
  txtInfoCommand->setBuddy(infoCommand);

  gbox31->addRowSpacing(0,grpInfoConf->fontMetrics().height());
  gbox31->addColSpacing(0,20);
  gbox31->addMultiCellWidget(txtUpdateInterval,1,1,0,1,Qt::AlignLeft);
  gbox31->addWidget(updateInterval,1,2,Qt::AlignLeft);
  gbox31->addMultiCellWidget(txtInfoChoose,2,2,0,2);
  gbox31->addMultiCellWidget(rbInfoDefault,3,3,0,2);
  gbox31->addMultiCellWidget(pureftpwhoDefault,4,4,1,2);
  gbox31->addWidget(txtPureftpwhoPath,5,1);
  gbox31->addWidget(pureftpwhoPath,5,2);
  gbox31->addMultiCellWidget(rbInfoCustom,6,6,0,2);
  gbox31->addWidget(txtInfoCommand,7,1);    
  gbox31->addWidget(infoCommand,7,2);    

  QButtonGroup *grpLogConf = new QButtonGroup(i18n(" Log Display Settings "),tabConfig);
  QGridLayout *gbox32 = new QGridLayout(grpLogConf,7,3,5);

  QLabel *txtHistoryLines = new QLabel(i18n("&Number of &history lines:"),grpLogConf);
  historyLines = new KIntNumInput(grpLogConf);
  historyLines->setRange(1,30000,1,false);
  txtHistoryLines->setBuddy(historyLines);
  QLabel *txtLogChoose = new QLabel(i18n("Choose logging method:"),grpLogConf);
  rbLogFile = new QRadioButton(i18n("D&isplay log file"),grpLogConf);
  txtLogFileName = new QLabel(i18n("Log Filename:"),grpLogConf);
  logFileName = new KURLRequester("",grpLogConf);
#if KDE_VERSION > 300
  logFileName->setMode(KFile::File);
#endif
  txtLogFileName->setBuddy(logFileName);
  rbLogCustom = new QRadioButton(i18n("Run cu&stom command"),grpLogConf);
  txtLogCommand = new QLabel(i18n("Log Command:"),grpLogConf);    
  logCommand = new KLineEdit(grpLogConf);
  txtLogCommand->setBuddy(logCommand);

  gbox32->addRowSpacing(0,grpLogConf->fontMetrics().height());
  gbox32->addColSpacing(0,20);
  gbox32->addMultiCellWidget(txtHistoryLines,1,1,0,1);
  gbox32->addWidget(historyLines,1,2,Qt::AlignLeft);
  gbox32->addMultiCellWidget(txtLogChoose,2,2,0,2);
  gbox32->addMultiCellWidget(rbLogFile,3,3,0,2);
  gbox32->addWidget(txtLogFileName,4,1);
  gbox32->addWidget(logFileName,4,2);
  gbox32->addMultiCellWidget(rbLogCustom,5,5,0,2);
  gbox32->addWidget(txtLogCommand,6,1);
  gbox32->addWidget(logCommand,6,2);

  rbInfoDefault->setChecked(true);
  pureftpwhoDefault->setChecked(true);
  txtPureftpwhoPath->setEnabled(false);
  pureftpwhoPath->setEnabled(false);
  txtInfoCommand->setEnabled(false);
  infoCommand->setEnabled(false);
  rbLogFile->setChecked(true);
  txtLogCommand->setEnabled(false);
  logCommand->setEnabled(false);

  vbox3->addWidget(grpInfoConf);
  vbox3->addSpacing(5);
  vbox3->addWidget(grpLogConf);
  vbox3->addStretch();

  connect(updateInterval,SIGNAL(valueChanged(int)),this,SLOT(slotChangedInterval(int)));
  connect(rbInfoDefault,SIGNAL(toggled(bool)),this,SLOT(slotInfoConfDefault(bool)));
  connect(rbLogFile,SIGNAL(toggled(bool)),this,SLOT(slotLogConfDefault(bool)));
  connect(pureftpwhoDefault,SIGNAL(toggled(bool)),this,SLOT(slotInfoConfUseDefault(bool)));
}


void KPureftpdStat::displayInfoMessage(InfoMessage t) {
  switch (t) {
    case InfoInit:
      mesgInfoIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_info",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;    
    case InfoExecCommandKill:
    case InfoExecRunning:
    case InfoWarning:
      mesgInfoIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_warning",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;
    case InfoExecCommandError:
      mesgInfoIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_critical",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;
    case InfoClear:
    default:
      mesgInfoIcon->setPixmap(QPixmap());
  }

  switch (t) {
    case InfoClear:
      mesgInfoText->setText("");
    break;
    case InfoInit:
      mesgInfoText->setText(i18n(
            "<qml>Press <b>Continuos Update</b> button to view "
            "FTP connections to your PureFTPd server. Depress the "
            "button to freeze view on current state.</qml>"
            ));
    break;
    case InfoWarning:
    break;
    case InfoExecRunning:
      mesgInfoText->setText(i18n(
            "<qml>The process for FTP connection displaying "
            "have been locked, timed out or took to long to process. Please "
            "try to increase the update interval or ask your system administrator "
            "to investigate the issue.</qml>"
            ));
    break;
    case InfoExecCommandError:
      mesgInfoText->setText(i18n(
            "<qml>There was an error executing FTP info grabbing command. "
            "Check if this error is temporary or permanent, then "
            "contact your system administrator.</qml>"
            ));
    break;
    case InfoExecCommandKill:
      mesgInfoText->setText(i18n(
            "<qml>The info grabbing process has been forced to stop "
            "because there's more time needed for FTP sessions processing "
            "than time interval between updates.</qml>"
            ));
    break;
    default:
      kdWarning() << __FUNCTION__  << "(): unknown message display type t=" << t << endl;
  }
}

void KPureftpdStat::displayLogMessage(LogMessage t) {
  switch (t) {
    case LogInit:
    case LogFull:
      mesgLogIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_info",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;    
    case LogWriteError:
    case LogExecTerminated:
      mesgLogIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_warning",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;
    case LogExecTailError:
    case LogExecCommandError:
      mesgLogIcon->setPixmap(KApplication::kApplication()->iconLoader()->loadIcon(
        "messagebox_critical",KIcon::NoGroup,KIcon::SizeMedium,KIcon::DefaultState,0,true));
    break;
    case LogClear:
    default:
      mesgLogIcon->setPixmap(QPixmap());
  }

  switch (t) {
    case LogClear:
      mesgLogText->setText("");
    break;
    case LogInit:
      mesgLogText->setText(i18n(
            "<qml>Press <b>Continuos Update</b> button to view "
            "PureFTPd server log file in real-time. Depress the "
            "button to freeze view on current state.</qml>"
            ));
    break;
    case LogWriteError:
      mesgLogText->setText(i18n(
          "<qt>You don't have permission to write the log "
          "file here or some serious write error "
          "happened.<p>Please verify and try again.</qt>"));
    break;
    case LogExecTailError:
      mesgLogText->setText(i18n(
            "<qt>There was an error executing UNIX <b>tail</b> "
            "process. Please verify if you have permissions executing "
            "it and if it's accessible from environment variable "
            "<tt>PATH</tt>.</qt>"));
    break;
    case LogExecCommandError:
      mesgLogText->setText(i18n(
            "<qt>There was an error running command line "
            "for log reading. Please verify if you have "
            "all necessary permissions or if there's an error "
            "in command line.</qt>"));
    break;
    case LogExecTerminated:
      mesgLogText->setText(i18n(
            "<qt>The UNIX process for reading log file "
            "has terminated or has been killed. Check "
            "the logging command line.</qt>"));
    break;
    case LogFull:
      mesgLogText->setText(i18n(
            "<qt>The maximum number of logged lines "
            "has been reached. Please press <b>Clear</b> "
            "button to be able to start logging again. "
            "If you want more lines logged increase the "
            "\"Number of history lines\" counter in "
            "<b>Configuration</b> tab.</qt>"));
    break;
    default:
      kdWarning() << __FUNCTION__  << "(): unknown message display type t=" << t << endl;
  }
}


//----------------------  SLOTS  ---------------------//
void KPureftpdStat::configChanged() {
    emit changed(true);
}

void KPureftpdStat::configChanged(const QString &text) {
    QString dummy=text;
    configChanged();
}

void KPureftpdStat::configChanged(int number) {
    int dummy;
    dummy=number;
    configChanged();
}

// "Log" tab
void KPureftpdStat::slotLogSearch() {
  int dummy = (!findBackwards->isChecked()) ? 0 : INT_MAX;
  if (!mFound) {
    mFound=!logOutput->find(editSearch->text(),caseSensitive->isChecked(),wholeWords->isChecked(),
                           !findBackwards->isChecked());
  } else {
    mFound=!logOutput->find(editSearch->text(),caseSensitive->isChecked(),wholeWords->isChecked(),
                           !findBackwards->isChecked(),&dummy,&dummy);
  }
}

void KPureftpdStat::slotLogResetSearch() {
  mFound=false;
  logOutput->setCursorPosition(0,0,false);
}


void KPureftpdStat::slotLogSave() {
  displayLogMessage(LogClear);
  
  KURL urlName = KFileDialog::getSaveURL(startDir+"/"+fileName,
                i18n("*.log|Log Files\n"
                     "*.txt|Text Files\n"
                     "*|All Files"),0,
                i18n("Save Log Output"));
  if (!urlName.isEmpty()) {
    startDir = urlName.directory(true,true);
    fileName = urlName.filename();
    if (!fileName.isEmpty()) {
      QFile f(startDir+"/"+fileName);
      if (f.open(IO_WriteOnly)) {
        QTextStream t(&f);
        t << logOutput->text();
        f.close();
      } else {
        displayLogMessage(LogWriteError);
      }
    }
  }

  config->setGroup("Log Tab");
  config->writeEntry("Log Saving Directory",startDir);
  config->writeEntry("Log FileName",fileName);
  config->sync();
}

void KPureftpdStat::slotLogCopy() {
  displayLogMessage(LogClear);
  QApplication::clipboard()->setText(logOutput->text());
}

void KPureftpdStat::slotLogClear() {
  displayLogMessage(LogClear);
  logOutput->clear();
  logUpdate->setEnabled(true);
}


void KPureftpdStat::slotLogProcessToggleUpdate(bool toggled) {
  if (toggled) {
    displayLogMessage(LogClear);
    startLogProcess();
  } else {
    stopLogProcess();
    logOutput->append(i18n("---- Stopped ----"));
  }
}


void KPureftpdStat::startLogProcess()
{
  if (logProcess->isRunning()) {
    kdWarning() << __FUNCTION__ << "(): the log process is already running" << endl;
    return;
  }

  tmpStdout=QString::null;
  tmpStderr=QString::null;
  logProcess->clearArguments();
  if (rbLogFile->isChecked()) {
    *logProcess << "tail" << " -n 1 -f " << KShellProcess::quote(logFileName->url());
  } else {
    *logProcess << logCommand->text().stripWhiteSpace();
  }

  bool started=logProcess->start(KProcess::NotifyOnExit,KProcess::AllOutput);

  if (started) {
    reachedMaxNumLines=false;
    logMaxNumLines=historyLines->value();
  } else {
    if (rbLogFile->isChecked()) {
      displayLogMessage(LogExecTailError);
    } else {
      displayLogMessage(LogExecCommandError);
    }
    logUpdate->setOn(false);
  }
}


void KPureftpdStat::stopLogProcess()
{
  int countdown=2000;
  
  logProcess->closeStdout();
  logProcess->closeStderr();
  if (logProcess->isRunning()) {
    logProcess->kill(SIGKILL);
    while (countdown && logProcess->isRunning()) {
      kapp->processEvents();
      usleep(200000);
      countdown--;
    }
    if (countdown==0) {
      kdWarning() << __FUNCTION__ << "(): The logging process has not exited! pid=" << logProcess->pid() << endl;
    }
  }
}


void KPureftpdStat::slotLogProcessRecvStdout(KProcess *p, char *buf, int len) {
  KProcess *dummy;
  dummy=p;

  QString m_buf = tmpStdout + QString::fromLatin1(buf,len);
  int pos = m_buf.findRev("\n");
  if (pos == (int) m_buf.length()-1) {
    logOutput->append(m_buf.left(pos));
    tmpStdout=QString::null;
  } else if (pos == -1) {
    tmpStdout=m_buf;
  } else {
    logOutput->append(m_buf.left(pos));
    tmpStdout=m_buf.mid(pos+1);
  }

  if (logOutput->numLines()>=logMaxNumLines) {
    reachedMaxNumLines=true;
    // flush the line buffer
    if (pos == -1) {
      logOutput->append(tmpStdout);
    }
    displayLogMessage(LogFull);
    logUpdate->setEnabled(false);
    logUpdate->setOn(false);
    return;
  }
}

void KPureftpdStat::slotLogProcessRecvStderr(KProcess *p, char *buf, int len) {
  KProcess *dummy;
  dummy=p;

  QString m_buf = tmpStderr+QString::fromLatin1(buf,len);
  int pos  =  m_buf.findRev("\n");
  if (pos == (int) (m_buf.length()-1)) {
    logOutput->append(m_buf.left(pos));
    tmpStderr=QString::null;
  } else if (pos == -1) {
    tmpStderr=m_buf;
  } else {
    logOutput->append(m_buf.left(pos));
    tmpStderr=m_buf.mid(pos+1);
  }
  
  if (logOutput->numLines()>=logMaxNumLines) {
    reachedMaxNumLines=true;
    // flush the line buffer
    if (pos == -1) {
      logOutput->append(tmpStderr);
    }
    displayLogMessage(LogFull);
    logUpdate->setEnabled(false);
    logUpdate->setOn(false);
    return;
  }
}

void KPureftpdStat::slotLogProcessExited(KProcess *p) {
  if (!reachedMaxNumLines) {
    if (p->normalExit() && ! p->exitStatus()) {
      displayLogMessage(LogExecTerminated);
    } else {
      if (logUpdate->isOn()) {
        displayLogMessage(LogExecCommandError);
      }
    }
  }
  if (logUpdate->isOn()) {
    logUpdate->setOn(false);
  }
}


// "Info" tab
void KPureftpdStat::slotInfoProcessToggleUpdate() {
  if (infoUpdate->isOn()) {
    txtExtInfo->setText("");
    ftpSessionMap.clear();
    ftpGuiMap.clear();
    connList->clear();
    txtExtInfo->setPaper(QBrush(qApp->palette().active().base()));
    displayInfoMessage(InfoClear);
    pureftpwhoExe=pureftpwhoPath->url();
    startInfoProcess();
    infoTimer->start(1000*updateInterval->value());
  } else {
    deliberatedKill=true;
    infoTimer->stop();
    stopInfoProcess();
  }
}

void KPureftpdStat::startInfoProcess() {
  if (isProcessingInfo) {
      displayInfoMessage(InfoExecRunning);
  } else {
    isProcessingInfo=true;
    infoProcess->clearArguments();
    if (rbInfoDefault->isChecked()) {
      if (pureftpwhoDefault->isChecked()) {
        *infoProcess << pureftpwhoDefaultPath << "-x";
      } else {
        *infoProcess << pureftpwhoExe << "-x";
      }
    } else {
      *infoProcess << infoCommand->text().stripWhiteSpace();
    }

    bool started = infoProcess->start(KProcess::NotifyOnExit,KProcess::AllOutput);

    if (started) {
      deliberatedKill=false;
      infoOutput = QString::null;
    } else {
      infoUpdate->setOn(false);
      displayInfoMessage(InfoExecCommandError);
    }
  }
}

void KPureftpdStat::stopInfoProcess() {
  int countdown=2000;
  
  infoProcess->closeStdout();
  infoProcess->closeStderr();
  if (infoProcess->isRunning()) {
    infoProcess->kill(SIGKILL);
    while (countdown && infoProcess->isRunning()) {
      kapp->processEvents();
      usleep(200000);
      countdown--;
    }
    if (countdown==0) {
      kdWarning() << __FUNCTION__ << "(): The info process has not exited! pid=" << infoProcess->pid() << endl;
    }
  }
}


void KPureftpdStat::slotInfoProcessRecvStdout(KProcess *p, char *buf, int len) {
  KProcess *dummy_p;
  int dummy_len;

  dummy_p=p;
  dummy_len=len;
  infoOutput.append(buf);
}

void KPureftpdStat::slotInfoProcessRecvStderr(KProcess *p, char *buf, int len) {
  KProcess *dummy_p;
  int dummy_len;
  char *dummy_buf;
  
  dummy_p=p;
  dummy_len=len;
  dummy_buf=buf;

  // nothing usable do with STDERR for the moment 
}

void KPureftpdStat::slotInfoProcessExited(KProcess *p) {
  KProcess *dummy;
  dummy=p;

  if (infoProcess->normalExit()) {
    // infoProcess terminated its execution
    if (infoProcess->exitStatus()) {
      // terminated with some error
      displayInfoMessage(InfoExecCommandError);
      if (infoTimer->isActive()) {
        infoTimer->stop();
      }
      infoUpdate->setOn(false);
    } else {
      // terminated OK, so process the received data
      processInfoOutput();
    }
  } else {
    // infoProcess was killed
    if (! deliberatedKill) {
      displayInfoMessage(InfoExecCommandKill);
    }
  }
  isProcessingInfo=false;
}

QString KPureftpdStat::prettyByte(unsigned long long m_byte) {
  QString ext;

  if (m_byte==1) return i18n("1 Byte");
  if (m_byte==0) return i18n("0 Bytes");
  if (m_byte >= 1024) {
      double m_kbyte = m_byte / 1024.0;
      if (m_kbyte >= 1024) {
        double m_mbyte = m_kbyte / 1024.0;
        if (m_mbyte >= 1024) {
          double m_gbyte = m_mbyte / 1024.0;
          QString s_gbyte;
          s_gbyte.sprintf("%4.2f",m_gbyte);
          return i18n("%1 GBytes").arg(s_gbyte);
        } else {
          QString s_mbyte;
          s_mbyte.sprintf("%4.2f",m_mbyte);
          return i18n("%1 MBytes").arg(s_mbyte);
        }
      } else {
        QString s_kbyte;
        s_kbyte.sprintf("%4.2f",m_kbyte);
        return i18n("%1 KBytes").arg(s_kbyte);
      }
  } else {
    return i18n("%1 Bytes").arg((unsigned long)m_byte);
  }
  return QString::null;
}

void KPureftpdStat::slotShowExtendedInfo(QListViewItem *item) {
  if (!item) return;
  
  FTPSessionItem f;
  bool itemFound=false;
  
  for (FtpGuiMap::Iterator it=ftpGuiMap.begin(); it!=ftpGuiMap.end(); ++it) {
    if (*it == item) {
      f=ftpSessionMap[it.key()];
      itemFound=true;
      break;
    }
  }
  
  if (!itemFound) {
    kdWarning() << __FUNCTION__ << "(): clicked QListViewItem was not found in ftpGuiMap! ptr=" << item << endl;
    return;
  }
  
  txtExtInfo->setPaper(QBrush(kapp->palette().active().base()));
  switch (f.state()) {
    case FTPSessionItem::Idle:
      txtExtInfo->setText(i18n(
          "<qml><b>Local Host:</b> %1:%2<br>"
          "<b>Remote Host:</b> %3<br>"
          "</qml>"
          ).arg(f.localhost()).arg(f.port()).arg(f.host()));
    break;
    case FTPSessionItem::Error:
      txtExtInfo->setText(i18n(
          "<qml>There is an FTP error on this connection. In short "
          "time connection should close.</qml>"
          ));
    break;
    case FTPSessionItem::Download:
    case FTPSessionItem::Upload:
      txtExtInfo->setText(i18n(
          "<qml><b>File:</b> %1<br>"
          "<b>Local Host:</b> %2:%3 &nbsp; <b>Remote Host:</b> %4<br>"
          "<b>Current Size:</b> %5 of %6 (resumed at %7)<br>"
          "<b>Bandwidth:</b> %8/s<br>"
          "</qml>"
          ).arg(f.file()).arg(f.localhost()).arg(f.port()).arg(f.host())
           .arg(prettyByte(f.current_size())).arg(prettyByte(f.total_size()))
           .arg(prettyByte(f.resume())).arg(prettyByte(f.bandwidth())));
    break;
    case FTPSessionItem::Unknown:
      txtExtInfo->setText(i18n(
          "<qml>Unknown FTP session state. There may be incompatibility "
          "between <b>pure-ftpd</b> and this application.</qml>"
          ));
    break;
    default:
      kdWarning() << __FUNCTION__ << "(): Unknown FTP session state=" << f.state() << endl;
  }

}


void KPureftpdStat::processInfoOutput() {
  FtpSessionMap tmp_ftpSessionMap;
  FTPSessionItem::Status attr_state;
  pid_t attr_pid;
  int attr_percentage;
  unsigned long attr_time, attr_bandwidth;
  unsigned long long attr_resume, attr_current_size, attr_total_size;
  QString tmp, attr_account, attr_file, attr_host, attr_localhost, attr_port;
  QDomDocument doc;
  KListViewItem *item;


  doc.setContent(infoOutput);
  QDomElement docElem0 = doc.documentElement();
  QDomNode n = docElem0.firstChild();

  while (!n.isNull()) {
    QDomElement docElem = n.toElement();
    if (!docElem.isNull()) {
      tmp = docElem.attribute("state","");
      if (tmp == "IDLE") {
        attr_state = FTPSessionItem::Idle;
      } else if (tmp == "DOWNLOAD" || tmp == " DL ") {
        attr_state = FTPSessionItem::Download;
      } else if (tmp == "UPLOAD" || tmp == " UL ") {
        attr_state = FTPSessionItem::Upload;
      } else if (tmp == "ERROR" || tmp == "ERR!") {
        attr_state = FTPSessionItem::Error;
      } else {
        attr_state = FTPSessionItem::Unknown;
      }
      attr_account      = docElem.attribute("account","");
      attr_file         = docElem.attribute("file","");
      attr_host         = docElem.attribute("host","");
      attr_localhost    = docElem.attribute("localhost","");
      attr_port         = docElem.attribute("localport","");
      tmp               = docElem.attribute("resume","0");
      attr_resume       = tmp.toULong();
      tmp               = docElem.attribute("current_size","0");
      attr_current_size = tmp.toULong();
      tmp               = docElem.attribute("total_size","0");
      attr_total_size   = tmp.toULong();
      tmp               = docElem.attribute("time","0");
      attr_time         = tmp.toULong();
      tmp               = docElem.attribute("bandwidth","0");
      attr_bandwidth    = tmp.toULong();
      tmp               = docElem.attribute("percentage","0");
      attr_percentage   = tmp.toULong();
      tmp               = docElem.attribute("pid","0");
      attr_pid          = tmp.toInt();
      FTPSessionItem ftpItem(attr_percentage, attr_pid, attr_state,
                             attr_time, attr_bandwidth,
                             attr_resume, attr_current_size, attr_total_size,
                             attr_account, attr_file, attr_host,
                             attr_localhost, attr_port);
      tmp_ftpSessionMap[attr_pid]=ftpItem;
    }
    n = n.nextSibling();
  }

  // first, remove the items which are no longer
  FtpSessionMap::Iterator it=ftpSessionMap.begin();
  while (it != ftpSessionMap.end()) {
    if (!tmp_ftpSessionMap.contains(it.key())) {
      delete ftpGuiMap[it.key()];
      ftpGuiMap.remove(it.key());
      ftpSessionMap.remove(it);
      it=ftpSessionMap.begin();
    } else {
      ++it;
    }
  }

  // second, add the new elements & update the current ones
  for (FtpSessionMap::Iterator it=tmp_ftpSessionMap.begin(); it!=tmp_ftpSessionMap.end(); ++it) {
    QString tmp_state;
    switch ((*it).state()) {
      case FTPSessionItem::Idle:
        tmp_state = i18n("ftp state","Idle");
      break;
      case FTPSessionItem::Download:
        tmp_state = i18n("ftp state","Download");
      break;
      case FTPSessionItem::Upload:
        tmp_state = i18n("ftp state","Upload");
      break;
      case FTPSessionItem::Error:
        tmp_state = i18n("ftp state","Error");
      break;
      default:
        tmp_state = i18n("ftp state","Unknown");
    }
    long int total_sec = (*it).time();
    int m_hour =  (int) total_sec / 3600;
    int m_min  = (int) (total_sec - m_hour * 3600) / 60;
    int m_sec  = total_sec % 60;
    QString m_time;
    m_time.sprintf("%d:%02d:%02d",m_hour,m_min,m_sec);

    if (!ftpSessionMap.contains(it.key())) {
      // add new element
      item = new KListViewItem(connList,(*it).account(),tmp_state,m_time,QString::number((*it).percentage())+"%",(*it).file());
      ftpGuiMap[it.key()]=item;
    } else {
      // update the old one with current data
      ftpGuiMap[it.key()]->setText(0,(*it).account());
      ftpGuiMap[it.key()]->setText(1,tmp_state);
      ftpGuiMap[it.key()]->setText(2,m_time);
      ftpGuiMap[it.key()]->setText(3,QString::number((*it).percentage())+"%");
      ftpGuiMap[it.key()]->setText(4,(*it).file());
    }
    ftpSessionMap[it.key()]=tmp_ftpSessionMap[it.key()];
  }  

  bool ftpSessionIsActive=false;
  for (FtpGuiMap::Iterator it=ftpGuiMap.begin(); it!=ftpGuiMap.end(); ++it) {
    if (connList->isSelected(*it)) {
      slotShowExtendedInfo(*it);
      ftpSessionIsActive=true;
    }
  }
  if (ftpSessionIsActive) {
    txtExtInfo->setPaper(QBrush(kapp->palette().active().base()));
  } else {
    txtExtInfo->setPaper(QBrush(kapp->palette().active().background()));
  }
}


void KPureftpdStat::slotChangedInterval(int interval) {
  if (infoTimer->isActive()) {
    infoTimer->changeInterval(1000*interval);
  }
}


// "Configuration" tab
void KPureftpdStat::slotInfoConfDefault(bool b) {
  pureftpwhoDefault->setEnabled(b);
  pureftpwhoPath->setEnabled(b && !pureftpwhoDefault->isChecked());
  txtPureftpwhoPath->setEnabled(b && !pureftpwhoDefault->isChecked());
  txtInfoCommand->setEnabled(!b);
  infoCommand->setEnabled(!b);
}

void KPureftpdStat::slotInfoConfUseDefault(bool b) {
  txtPureftpwhoPath->setEnabled(!b);
  pureftpwhoPath->setEnabled(!b);
}

void KPureftpdStat::slotLogConfDefault(bool b) {
  logFileName->setEnabled(b);
  txtLogFileName->setEnabled(b);
  logCommand->setEnabled(!b);
  txtLogCommand->setEnabled(!b);
}



// KCMModule usual stuff

void KPureftpdStat::load() {
  config->setGroup("Find");
  caseSensitive->setChecked(config->readBoolEntry("Case Sensitive",false));
  wholeWords->setChecked(config->readBoolEntry("Whole Words",false));
  findBackwards->setChecked(config->readBoolEntry("Find Backwards",false));
  editSearch->setText(config->readEntry("Search String",QString::null));

  config->setGroup("Log Tab");
  startDir = config->readEntry("Log Saving Directory",QDir::currentDirPath());
  fileName = config->readEntry("Log Filename","pureftpd.log");

  config->setGroup("Options");
  updateInterval->setValue(config->readUnsignedNumEntry("Info Update Interval",3));
  infoTimer->changeInterval(1000*updateInterval->value());
  infoTimer->stop();
  
  historyLines->setValue(config->readUnsignedNumEntry("History Log Lines",500));
  logFileName->setURL(config->readEntry("Log FileName","/var/log/pureftpd.log"));
  pureftpwhoPath->setURL(config->readEntry("Pureftpwho Path","/usr/local/sbin/pure-ftpwho"));
  infoCommand->setText(config->readEntry("Custom Info Command",QString::null));
  logCommand->setText(config->readEntry("Custom Log Command",QString::null));
  pureftpwhoDefault->setChecked(config->readBoolEntry("Use Default Pureftpwho",true));
  rbInfoDefault->setChecked(config->readBoolEntry("Use Pureftpwho",true));
  rbInfoCustom->setChecked(!config->readBoolEntry("Use Pureftpwho",true));
  rbLogFile->setChecked(config->readBoolEntry("Use Log File",true));
  rbLogCustom->setChecked(!config->readBoolEntry("Use Log File",true));
  
  KConfig *tmp_config = new KConfig("kcmpureftpdrc",true,false);
  tmp_config->setGroup("Options");
  pureftpwhoDefaultPath=tmp_config->readEntry("Pureftpwho Path","/usr/local/sbin/pure-ftpwho");
  delete tmp_config;
  
  emit changed(false);
}

void KPureftpdStat::save() {
  config->setGroup("Find");
  config->writeEntry("Case Sensitive",caseSensitive->isChecked());
  config->writeEntry("Whole Words",wholeWords->isChecked());
  config->writeEntry("Find Backwards",findBackwards->isChecked());
  config->writeEntry("Search String",editSearch->text());

  config->setGroup("Options");
  config->writeEntry("Info Update Interval",updateInterval->value());
  config->writeEntry("History Log Lines",historyLines->value());
  config->writeEntry("Log FileName",logFileName->url());
  config->writeEntry("Pureftpwho Path",pureftpwhoPath->url());
  config->writeEntry("Custom Info Command",infoCommand->text());
  config->writeEntry("Custom Log Command",logCommand->text());
  config->writeEntry("Use Default Pureftpwho",pureftpwhoDefault->isChecked());
  config->writeEntry("Use Pureftpwho",rbInfoDefault->isChecked());
  config->writeEntry("Use Log File",rbLogFile->isChecked());

  config->sync();
  emit changed(false);
}


void KPureftpdStat::defaults() {
    load();
    emit changed(false);
}

int KPureftpdStat::buttons() {
    return KCModule::Default | KCModule::Apply | KCModule::Help;
}

QString KPureftpdStat::quickHelp() const {
    return i18n("<h1>PureFTPd Statistics</h1> <p>This module allow you to track statistics "
                "from <b>pure-ftpd</b> FTP server. You can see real time information about "
                "FTP connection on your FTP server as well you may follow information written "
                "to syslog by the server. <p>To obtain this high "
                "quality server go to <a href=\"http://www.pureftpd.org/\">http://www.pureftpd.org/</a>.</p>"
               );
}

const KAboutData *KPureftpdStat::aboutData() const {
    KAboutData *myAboutData = new KAboutData(
                                  "kcmpureftpdstat", I18N_NOOP("KCM PureFtpd Statistics"),
                                  VERSION, I18N_NOOP("KControl module for PureFtpd Statistics"),
                                  KAboutData::License_GPL, "(c) 2002, 2003 Claudiu Costin",0,
                                  "http://www.ro.kde.org/kcmpureftpd/",
                                  "claudiuc@kde.org");
    myAboutData->addAuthor("Claudiu Costin",I18N_NOOP("Original author"),"claudiuc@kde.org");
    myAboutData->addCredit("Iuliana Costin",
                           I18N_NOOP("My lovely wife who allowed me to spend countless hours in "
                                     "front of computer."),QString::null);
    myAboutData->addCredit("Frank Denis",
                           I18N_NOOP("Pureftpd FTP server author. "
                                     "Many thanks for such great piece of code."),
                                     "j@pureftpd.org");
    return myAboutData;
}

extern "C" {
    KCModule *create_pureftpdstat(QWidget *parent, const char *name) {
        QString dummy=name;
        KGlobal::locale()->insertCatalogue("kcmpureftpd");
        return new KPureftpdStat(parent,"kcmpureftpdstat");
    };

    void init_pureftpdstat() {
        kapp->startServiceByDesktopName("kcmpureftpdstat");
    };
}



Generated by  Doxygen 1.6.0   Back to index