Logo Search packages:      
Sourcecode: kmess version File versions

chathistorydialog.cpp

 /***************************************************************************
                          chathistorydialog.cpp  -  chat logs browser
                             -------------------
    begin                : Sun Feb 22 2009
    copyright            : (C) 2009 by Dario Freddi
    email                : drf54321@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "chathistorydialog.h"

#include "../chat/chatmessageview.h"
#include "../utils/kmessconfig.h"
#include "../currentaccount.h"

#include <QDir>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QtXml/QDomDocument>
#include <QtXml/QDomNode>

#include <KDebug>
#include <KMessageBox>
#include <KLocalizedString>
#include <KDateTime>



// Constructor
ChatHistoryDialog::ChatHistoryDialog( QWidget *parent )
: KDialog( parent )
, model_( new QStandardItemModel( this ) )
, proxyModel_( new QSortFilterProxyModel( this ) )
{
  // Set up the dialog
  QWidget *mainWidget = new QWidget( this );
  setupUi( mainWidget );
  setMainWidget( mainWidget );
  setButtons( Close );
  setWindowTitle( i18nc( "Dialog window title", "Chat History" ) );

  // Let the dialog destroy itself when it's done
  setAttribute( Qt::WA_DeleteOnClose );

  // Create the chat view to show the chat logs
  chatView_ = new ChatMessageView( mainWidget );
  chatView_->updateChatStyle();
  rightSplitter_->addWidget( chatView_->widget() );

  // Make the splitter assign more space to the view than to the controls,
  // and set its minimum size
  QSizePolicy sizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
  sizePolicy.setVerticalStretch( 100 );
  chatView_->widget()->setMinimumSize( 300, 200 );
  chatView_->widget()->setSizePolicy( sizePolicy );

  loadContactsList();

  // Initialize the model for the list of contacts
  proxyModel_->setSourceModel( model_ );
  proxyModel_->setDynamicSortFilter( true );
  proxyModel_->setFilterCaseSensitivity( Qt::CaseInsensitive );

  listView_->setModel( proxyModel_ );

  QItemSelectionModel *selectionModel = listView_->selectionModel();

  // Attach the signals
  connect( searchEdit_,           SIGNAL(       textChanged(const QString&)                     ),
           proxyModel_,           SLOT  (         setFilterRegExp(const QString&)               ) );
  connect( selectionModel,        SIGNAL( currentChanged(const QModelIndex&,const QModelIndex&) ),
           this,                  SLOT  (         cacheContactXml(const QModelIndex&)           ) );
  connect( dateRadio_,            SIGNAL(       toggled(bool)                                   ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( fromBox_,              SIGNAL(       toggled(bool)                                   ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( fromDate_,             SIGNAL(       changed(const QDate&)                           ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( toBox_,                SIGNAL(       toggled(bool)                                   ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( toDate_,               SIGNAL(       changed(const QDate&)                           ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( conversationRadio_,    SIGNAL(       toggled(bool)                                   ),
           this,                  SLOT  (         reloadLogs()                                  ) );
  connect( conversationComboBox_, SIGNAL(       currentIndexChanged(int)                        ),
           this,                  SLOT  (         reloadLogs()                                  ) );

  // Save the dialog and splitters sizes
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "AddEmoticonDialog" );
  restoreDialogSize( group );
  mainSplitter_ ->setSizes( group.readEntry( "mainSplitterSizes",  QList<int>() << 1 << 100 ) );
  rightSplitter_->setSizes( group.readEntry( "rightSplitterSizes", QList<int>() << 1 << 100 ) );

  // Normally the focus would be set on the Search box, thus making the
  // preset explanation text to disappear.
  listView_->setFocus();
}



// Destructor
ChatHistoryDialog::~ChatHistoryDialog()
{
  // Save the dialog and splitters size
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "AddEmoticonDialog" );
  saveDialogSize( group );
  group.writeEntry( "mainSplitterSizes",  mainSplitter_ ->sizes() );
  group.writeEntry( "rightSplitterSizes", rightSplitter_->sizes() );
}



// Set the contact for whom the logs will be initially shown
void ChatHistoryDialog::setContact( const QString &handle )
{
  if( handle.isEmpty() )
  {
    return;
  }

  QList<QStandardItem*> items = model_->findItems( handle, Qt::MatchContains );

  if( items.isEmpty() )
  {
    kWarning() << "Unable to find contact" << handle;
    return;
  }

  listView_->setCurrentIndex( items.first()->index() );
}



// Caches the XML for the selected contact. Makes things a lot faster
void ChatHistoryDialog::cacheContactXml( const QModelIndex &index )
{
  if( ! proxyModel_->mapToSource( index ).isValid() )
  {
    return;
  }

  // Initially the list view emits a currentChanged() signal, but the list
  // doesn't bother to show the selected item
  listView_->setCurrentIndex( index );

  xml_.clear();

  QDir logsDir( KMessConfig::instance()->getAccountDirectory( CurrentAccount::instance()->getHandle() )
                + "/chatlogs" );

  logsDir.setFilter( QDir::Files );
  logsDir.setNameFilters( QStringList() << "*.xml" );

  const QFileInfoList &list( logsDir.entryInfoList() );
  foreach( const QFileInfo &entry, list )
  {
    QString handle( entry.fileName() );

    if( ! handle.startsWith( model_->itemFromIndex( proxyModel_->mapToSource( index ) )->text() ) )
    {
      continue;
    }

    QFile file( entry.absoluteFilePath() );

    if( ! file.open( QFile::ReadOnly | QFile::Text ) )
    {
      // Very unlikely, but...
      KMessageBox::error( this, i18nc( "Dialog box text",
                                       "There has been an error while opening your logs. This "
                                       "is commonly a permission problem, check if you have "
                                       "read/write access to directory <i>\"%1\"</i>. Otherwise, "
                                       "your logs may be corrupted.",
                                       logsDir.absolutePath() ),
                                i18nc( "Dialog box title", "Could not open chat history" ) );
      return;
    }

    xml_ += file.readAll();

    file.close();
  }

  // After caching, we want to load the conversation list and refresh the view
  loadConversationList();
  reloadLogs();
}



// Load the list of conversation for the selected contact
void ChatHistoryDialog::loadConversationList()
{
  conversationComboBox_->clear();

  QDomDocument doc;
  doc.setContent( xml_ );

  for( int i = 0; i < doc.childNodes().size(); ++i )
  {
    if( doc.childNodes().at(i).nodeName() == "messageRoot" )
    {
      QDomNode rootNode( doc.childNodes().at( i ) );
      for( int j = 0; j < rootNode.childNodes().size(); ++j )
      {
        if( rootNode.childNodes().at(j).nodeName() == "conversation" )
        {
          QDomNode conversation( rootNode.childNodes().at( j ) );

          if ( conversation.attributes().item( 0 ).nodeName() == "timestamp" )
          {
            // New system, good
            QDateTime timestamp = QDateTime::fromTime_t( conversation.attributes().namedItem( "timestamp" ).nodeValue().toInt() );
            conversationComboBox_->addItem( KGlobal::locale()->formatDateTime( timestamp, KLocale::ShortDate ),
                                            conversation.attributes().namedItem( "timestamp" ).nodeValue().toInt() );
          }
          else
          {
            // Old logs, bad.

            migrateOldLogs();
            return;
          }
        }
      }
    }
  }
}



// Load the list of contacts for which logs are available
void ChatHistoryDialog::loadContactsList()
{
  // Find all XML chat log files in the logs directory
  QDir logsDir( KMessConfig::instance()->getAccountDirectory( CurrentAccount::instance()->getHandle() )
                + "/chatlogs" );

  logsDir.setFilter( QDir::Files );
  logsDir.setNameFilters( QStringList() << "*.xml" );

  // Add them to the list
  const QFileInfoList &list( logsDir.entryInfoList() );
  foreach( const QFileInfo &entry, list )
  {
    QString handle( entry.fileName() );
    handle.remove( handle.length() - 4, 4 );

    // Files ending with .1 should not be added
    if( handle.at( handle.length() - 1 ).isNumber() )
    {
      continue;
    }

    model_->appendRow( new QStandardItem( handle ) );
  }
}



// Reload the current chat log history
void ChatHistoryDialog::reloadLogs()
{
  QDomDocument doc;
  doc.setContent( xml_ );

  if( dateRadio_->isChecked() )
  {
    for( int i = 0; i < doc.childNodes().size(); ++i )
    {
      if( doc.childNodes().at(i).nodeName() == "messageRoot" )
      {
        QDomNode rootNode = doc.childNodes().at(i);
        for( int j = 0; j < rootNode.childNodes().size(); ++j )
        {
          if( rootNode.childNodes().at(j).nodeName() == "conversation" )
          {
            QDomNode conversation( rootNode.childNodes().at( j ) );

            QDate date;

            if ( conversation.attributes().item( 0 ).nodeName() == "timestamp" )
            {
              // New system, good
              QDateTime timestamp = QDateTime::fromTime_t( conversation.attributes().item( 0 ).nodeValue().toInt() );
              date = timestamp.date();
            }
            else
            {
              // Old logs, bad. Try migrating them

              migrateOldLogs();
              return;
            }

            if ( !date.isValid() ) {
              KMessageBox::error( this, i18n( "Could not retrieve a valid date from your logs! This is "
                  "probably due to the fact that this log was generated with an "
                  "old KMess version" ) );
              break;
            }

            if( ( fromBox_->isChecked() && date < fromDate_->date() )
                ||  ( toBox_  ->isChecked() && date > toDate_  ->date() ) )
            {
              rootNode.removeChild( conversation );
            }
          }
        }
      }
    }
  }

  if( conversationRadio_->isChecked() )
  {
    for( int i = 0; i < doc.childNodes().size(); ++i )
    {
      if( doc.childNodes().at(i).nodeName() == "messageRoot" )
      {
        QDomNode rootNode( doc.childNodes().at( i ) );

        for( int j = 0; j < rootNode.childNodes().size(); ++j )
        {
          if( rootNode.childNodes().at( j ).nodeName() == "conversation" )
          {
            QDomNode conversation = rootNode.childNodes().at( j );

            if ( conversation.attributes().item( 0 ).nodeName() == "timestamp" )
            {
              // New system, good
              uint timestamp = conversation.attributes().item( 0 ).nodeValue().toInt();
              if ( timestamp != conversationComboBox_->itemData( conversationComboBox_->currentIndex(), Qt::UserRole ).toUInt() )
              {
                rootNode.removeChild( conversation );
              }
            }
            else
            {
              // Old logs, bad.

              migrateOldLogs();
              return;
            }
          }
        }
      }
    }
  }

  chatView_->setXml( doc.toString() );
}



// Migrates old logs to new timestamp format
void ChatHistoryDialog::migrateOldLogs()
{
  /* This is some sort of black magic: knowing what the local format
   * is, we try to retreive the date and the time, and then to convert
   * it to the timestamp
   */

  int result = KMessageBox::questionYesNo( this, i18n( "Your logs seems to be corrupted or belonging "
                                                       "to an old KMess version. KMess can try migrating "
                                                       "your logs. Do you want to do this now?" ) );

  if ( result == KMessageBox::No )
  {
    return;
  }

  int total = 0, migrated = 0, failed = 0;
  QStringList baseFormats = QStringList() << KGlobal::locale()->dateFormat()
                                          << KGlobal::locale()->dateFormatShort();
  QStringList formats;
  QStringList dateFormats = baseFormats;

  QString timeFormat = KGlobal::locale()->timeFormat();
  timeFormat.remove( ":%S:" );
  timeFormat.remove( ":%S" );
  timeFormat.remove( "%S:" );

  foreach ( const QString &format, baseFormats )
  {
    QString mFormat = format;
    mFormat.replace( "%n", "%:m" );
    QString dFormat = format;
    dFormat.replace( "%:m", "%m" );
    dateFormats << mFormat << dFormat;
    formats << format + ' ' + timeFormat
            << mFormat + ' ' + timeFormat
            << dFormat + ' ' + timeFormat;
  }

  kDebug() << "Date formats:" << formats << dateFormats;

  QDir logsDir( KMessConfig::instance()->getAccountDirectory( CurrentAccount::instance()->getHandle() )
      + "/chatlogs" );

  logsDir.setFilter( QDir::Files );
  logsDir.setNameFilters( QStringList() << "*.xml" );

  const QFileInfoList &list( logsDir.entryInfoList() );
  foreach( const QFileInfo &entry, list )
  {
    kDebug() << "migrating" << entry.absoluteFilePath();
    QFile file( entry.absoluteFilePath() );

    if( ! file.open( QFile::ReadOnly | QFile::Text ) )
    {
      // Very unlikely, but...
      KMessageBox::error( this, i18nc( "Dialog box text",
          "There has been an error while opening your logs. This "
          "is commonly a permission problem, check if you have "
          "read/write access to directory <i>\"%1\"</i>. Otherwise, "
          "your logs may be corrupted.",
          logsDir.absolutePath() ),
          i18nc( "Dialog box title", "Could not open chat history" ) );
      return;
    }

    QTextStream in(&file);
    QString newContents;
    while ( ! in.atEnd() )
    {
      QString line = in.readLine();

      if ( line.contains( "<conversation date" ) && line.contains( "date" ) )
      {
        ++total;

        QChar escapeChar = ( line.contains( '\'' ) ? '\'' : '"' );

        kDebug() << "Migrating line" << line;
        kDebug() << "Escape char is" << escapeChar;

        QString date = line.split( QString( "date=" ) + escapeChar ).at( 1 ).split( escapeChar ).at( 0 );

        bool success = false;

        foreach ( const QString &format, formats ) {
          if ( KDateTime::fromString( date, format ).isValid() )
          {
            ++migrated;
            success = true;
            line.replace( date, QString::number( KDateTime::fromString( date, format ).toTime_t() ) );
            line.replace( "date", "timestamp" );
            break;
          }
        }

        if ( ! success )
        {
          ++failed;
        }

        kDebug() << "new line is:" << line;
      }
      else if ( line.contains( "<message " ) && line.contains( "date" ) )
      {
        ++total;

        QChar escapeChar = ( line.contains( '\'' ) ? '\'' : '"' );

        kDebug() << "Migrating line" << line;
        kDebug() << "Escape char is" << escapeChar;

        QString date = line.split( QString( "date=" ) + escapeChar ).at( 1 ).split( escapeChar ).at( 0 );
        QString time = line.split( QString( "time=" ) + escapeChar ).at( 1 ).split( escapeChar ).at( 0 );

        bool success = false;

        foreach ( const QString &format, dateFormats ) {
          if ( KDateTime::fromString( date, format ).isValid() )
          {
            QDate dt = KDateTime::fromString( date, format ).date();
            QTime tm = KDateTime::fromString( time, timeFormat ).time();
            ++migrated;
            success = true;
            line.replace( time, QString::number( QDateTime( dt, tm ).toTime_t() ) );
            line.replace( "time", "timestamp" );
            break;
          }
        }

        if ( ! success )
        {
          ++failed;
        }

        kDebug() << "new line is:" << line;
      }
      newContents.append(line + '\n');
    }

    file.close();

    QFile::remove( entry.absoluteFilePath() );

    if( ! file.open( QFile::WriteOnly | QFile::Text ) )
    {
      // Very unlikely, but...
      KMessageBox::error( this, i18nc( "Dialog box text",
          "There has been an error while opening your logs. This "
          "is commonly a permission problem, check if you have "
          "read/write access to directory <i>\"%1\"</i>. Otherwise, "
          "your logs may be corrupted.",
          logsDir.absolutePath() ),
          i18nc( "Dialog box title", "Could not open chat history" ) );
      return;
    }

    QTextStream out(&file);
    out << newContents << "\n";

    file.close();
  }

  KMessageBox::information( this, i18n( "Migration terminated.\nTried to migrate %1 items\n"
                                            "%2 items successfully migrated\n"
                                            "%3 items could not have been migrated.",
                                            total, migrated, failed),
                                i18n( "Alpha logs migration" ) );

}


#include "chathistorydialog.moc"

Generated by  Doxygen 1.6.0   Back to index