/* -*- c-basic-offset: 2 -*-
 * kmail: KDE mail client
 * Copyright (c) 1996-1998 Stefan Taferner <taferner@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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */
#include "kmfoldermbox.h"

#include <config-kmail.h>
#include <QFileInfo>
#include <QList>
#include <QRegExp>
#include <QByteArray>

#include "folderstorage.h"
#include "kmfolder.h"
#include "kmkernel.h"
#include "kmmsgdict.h"
#include "undostack.h"
#include "kcursorsaver.h"
#include "jobscheduler.h"
#include "compactionjob.h"
#include "util.h"

#include <kde_file.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <knotification.h>
#include <kshell.h>
#include <kconfig.h>
#include <kconfiggroup.h>

#include <QDateTime>

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "broadcaststatus.h"
using KPIM::BroadcastStatus;

#ifndef MAX_LINE
#define MAX_LINE 4096
#endif
#ifndef INIT_MSGS
#define INIT_MSGS 8
#endif

// Regular expression to find the line that separates messages in a mail
// folder:
#define MSG_SEPERATOR_START "From "
#define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1)
#define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]"

#ifdef KMAIL_SQLITE_INDEX
#include <sqlite3.h>
#endif

//-----------------------------------------------------------------------------
KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name)
  : KMFolderIndex(folder, name)
{
  mStream         = 0;
  mFilesLocked    = false;
  mReadOnly       = false;
  mLockType       = lock_none;
}


//-----------------------------------------------------------------------------
KMFolderMbox::~KMFolderMbox()
{
  if ( mOpenCount > 0 ) {
    close( "~kmfoldermbox", true );
  }
  if ( kmkernel->undoStack() ) {
    kmkernel->undoStack()->folderDestroyed( folder() );
  }
}

//-----------------------------------------------------------------------------
int KMFolderMbox::open( const char *owner )
{
#ifdef FOLDER_REFCOUNT_DEBUGGING
  mOwners.append( owner );

  kDebug() << endl << "open" << mOpenCount << folder()->name()
           << mOwners << ", adding:" << owner;
//           << mOwners << ", adding:" << owner << kBacktrace();
#else
  Q_UNUSED( owner );
#endif
  int rc = 0;

  mOpenCount++;
  kmkernel->jobScheduler()->notifyOpeningFolder( folder() );

  if ( mOpenCount > 1 ) {
    return 0;  // already open
  }

  assert( !folder()->name().isEmpty() );

  mFilesLocked = false;
  mStream = KDE_fopen( QFile::encodeName( location() ), "rb+" ); // messages file
  if ( !mStream ) {
    KMessageBox::sorry( 0, i18n( "Cannot open file \"%1\":\n%2",
                                 location(), strerror( errno ) ) );
    kDebug() << "Cannot open folder `" << location() <<"':" << strerror(errno);
    mOpenCount = 0;
    return errno;
  }

  lock();

  rc = openInternal( CheckIfIndexTooOld | CreateIndexFromContentsWhenReadIndexFailed );
/* moved to openInternal()
  if ( !folder()->path().isEmpty() ) {
     KMFolderIndex::IndexStatus index_status = indexStatus();
     bool shouldCreateIndexFromContents = false;
     // test if index file exists and is up-to-date
     if ( KMFolderIndex::IndexOk != index_status ) {
       // only show a warning if the index file exists, otherwise it can be
       // silently regenerated
       if ( KMFolderIndex::IndexTooOld == index_status ) {
         QString msg = i18n("<qt><p>The index of folder '%2' seems "
                            "to be out of date. To prevent message "
                            "corruption the index will be "
                            "regenerated. As a result deleted "
                            "messages might reappear and status "
                            "flags might be lost.</p>"
                            "<p>Please read the corresponding entry "
                            "in the <a href=\"%1\">FAQ section of the manual "
                            "of KMail</a> for "
                            "information about how to prevent this "
                            "problem from happening again.</p></qt>",
                            QString("help:/kmail/faq.html#faq-index-regeneration"),
                            objectName());
        // When KMail is starting up we have to show a non-blocking message
        // box so that the initialization can continue. We don't show a
        // queued message box when KMail isn't starting up because queued
        // message boxes don't have a "Don't ask again" checkbox.
        if ( kmkernel->startingUp() ) {
          KConfigGroup configGroup( KMKernel::config(),
                                    "Notification Messages" );
          bool showMessage =
            configGroup.readEntry( "showIndexRegenerationMessage", true );
          if ( showMessage ) {
            KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
                                           msg, i18n("Index Out of Date"),
                                           KMessageBox::AllowLink );
          }
        } else {
          KCursorSaver idle( KBusyPtr::idle() );
          KMessageBox::information( 0, msg, i18n("Index Out of Date"),
                                    "showIndexRegenerationMessage",
                                    KMessageBox::AllowLink );
        }
       }
#ifdef KMAIL_SQLITE_INDEX
#else
       mIndexStream = 0;
#endif
       shouldCreateIndexFromContents = true;
       emit statusMsg( i18n("Folder `%1' changed. Recreating index.", objectName()) );
     } else {
#ifdef KMAIL_SQLITE_INDEX
#else
       mIndexStream = KDE_fopen( QFile::encodeName( indexLocation() ), "r+" ); // index file
       if ( mIndexStream ) {
# ifndef Q_WS_WIN
         fcntl( fileno( mIndexStream ), F_SETFD, FD_CLOEXEC );
# endif
         if ( !updateIndexStreamPtr() )
           return 1;
       }
       else
         shouldCreateIndexFromContents = true;
#endif
     }

     if ( shouldCreateIndexFromContents ) {
       rc = createIndexFromContents();
     } else {
       if ( !readIndex() ) {
         rc = createIndexFromContents();
       }
     }
  } else {
    mAutoCreateIndex = false;
    rc = createIndexFromContents();
  }

  mChanged = false;*/

#ifdef KMAIL_SQLITE_INDEX
#else
# ifndef Q_WS_WIN
  fcntl( fileno( mStream ), F_SETFD, FD_CLOEXEC );
  if ( mIndexStream ) {
     fcntl( fileno( mIndexStream ), F_SETFD, FD_CLOEXEC );
  }
# endif
#endif

  return rc;
}

//----------------------------------------------------------------------------
bool KMFolderMbox::canAccess() const
{
  assert(!folder()->name().isEmpty());

  QFileInfo finfo( location() );
  if ( !finfo.isReadable() || !finfo.isWritable() ) {
    kDebug() << "call to access function failed";
    return false;
  }
  return true;
}

//-----------------------------------------------------------------------------
int KMFolderMbox::create()
{
  int rc;
  int old_umask;

  assert(!folder()->name().isEmpty());
  assert(mOpenCount == 0);

  kDebug() << "Creating folder" << objectName();
  if (access(QFile::encodeName(location()), F_OK) == 0) {
    kDebug() << "call to access function failed.";
    kDebug() << "Error";
    return EEXIST;
  }

  old_umask = umask(077);
  mStream = KDE_fopen(QFile::encodeName(location()), "w+"); //sven; open RW
  umask(old_umask);

  if (!mStream) return errno;

#ifndef Q_WS_WIN
  fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
#endif

  rc = createInternal();

  if (!rc)
    lock();
  return rc;
}


//-----------------------------------------------------------------------------
void KMFolderMbox::reallyDoClose()
{
  if ( mAutoCreateIndex ) {
      if ( KMFolderIndex::IndexOk != indexStatus() ) {
        kDebug() << "Critical error:" << location()
                 << "has been modified by an external application while KMail was running.";
        //      exit(1); backed out due to broken nfs
      }

      updateIndex( true );
      writeConfig();
  }

  if ( !noContent() ) {
    if ( mStream ) {
      unlock();
    }
    mMsgList.clear( true );

    if ( mStream ) {
      fclose( mStream );
    }
#ifdef KMAIL_SQLITE_INDEX
    if ( mIndexDb )
      sqlite3_close( mIndexDb );
#else
    if ( mIndexStream ) {
      fclose( mIndexStream );
      updateIndexStreamPtr( true );
    }
#endif
  }
#ifdef KMAIL_SQLITE_INDEX
  mIndexDb = 0;
#else
  mIndexStream = 0;
#endif
  mOpenCount   = 0;
  mStream      = 0;
  mFilesLocked = false;
  mUnreadMsgs  = -1;

  mMsgList.reset( INIT_MSGS );
}

//-----------------------------------------------------------------------------
void KMFolderMbox::sync()
{
#ifdef KMAIL_SQLITE_INDEX
#else
  if (mOpenCount > 0)
    if (!mStream || fsync(fileno(mStream)) ||
        !mIndexStream || fsync(fileno(mIndexStream))) {
    kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2", indexLocation(), errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug.")));
    }
#endif
}

//-----------------------------------------------------------------------------
int KMFolderMbox::lock()
{
#ifdef Q_WS_WIN
# ifdef __GNUC__
#  warning TODO implement mbox locking on Windows
# else
#  pragma WARNING( TODO implement mbox locking on Windows )
# endif
  assert(mStream != 0);
  mFilesLocked = true;
  mReadOnly = false;
#else
  int rc;
  struct flock fl;
  fl.l_type=F_WRLCK;
  fl.l_whence=0;
  fl.l_start=0;
  fl.l_len=0;
  fl.l_pid=-1;
  QByteArray cmd_str;
  assert(mStream != 0);
  mFilesLocked = false;
  mReadOnly = false;

  switch( mLockType )
  {
    case FCNTL:
      rc = fcntl(fileno(mStream), F_SETLKW, &fl);

      if (rc < 0)
      {
        kDebug() << "Cannot lock folder `" << location() << "':"
                 << strerror(errno) << "(" << errno << ")";
        mReadOnly = true;
        return errno;
      }

#ifdef KMAIL_SQLITE_INDEX
#else
      if (mIndexStream)
      {
        rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);

        if (rc < 0)
        {
          kDebug() << "Cannot lock index of folder `" << location() << "':"
                   << strerror(errno) << "(" << errno << ")";
          rc = errno;
          fl.l_type = F_UNLCK;
          /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl);
          mReadOnly = true;
          return rc;
        }
      }
#endif
      break;

    case procmail_lockfile:
      cmd_str = "lockfile -l20 -r5 ";
      if (!mProcmailLockFileName.isEmpty())
        cmd_str += QFile::encodeName(KShell::quoteArg(mProcmailLockFileName));
      else
        cmd_str += QFile::encodeName(KShell::quoteArg(location() + ".lock"));

      rc = system( cmd_str.data() );
      if( rc != 0 )
      {
        kDebug() << "Cannot lock folder `" << location() << "':"
                 << strerror(rc) << "(" << rc << ")";
        mReadOnly = true;
        return rc;
      }
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KShell::quoteArg(indexLocation() + ".lock"));
        rc = system( cmd_str.data() );
        if( rc != 0 )
        {
          kDebug() << "Cannot lock index of folder `" << location() << "':"
                   << strerror(rc) << "(" << rc << ")";
          mReadOnly = true;
          return rc;
        }
      }
#endif
      break;

    case mutt_dotlock:
      cmd_str = "mutt_dotlock " + QFile::encodeName(KShell::quoteArg(location()));
      rc = system( cmd_str.data() );
      if( rc != 0 )
      {
        kDebug() << "Cannot lock folder `" << location() << "':"
                 << strerror(rc) << "(" << rc << ")";
        mReadOnly = true;
        return rc;
      }
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "mutt_dotlock " + QFile::encodeName(KShell::quoteArg(indexLocation()));
        rc = system( cmd_str.data() );
        if( rc != 0 )
        {
          kDebug() << "Cannot lock index of folder `" << location() << "':"
                   << strerror(rc) << "(" << rc << ")";
          mReadOnly = true;
          return rc;
        }
      }
#endif
      break;

    case mutt_dotlock_privileged:
      cmd_str = "mutt_dotlock -p " + QFile::encodeName(KShell::quoteArg(location()));
      rc = system( cmd_str.data() );
      if( rc != 0 )
      {
        kDebug() << "Cannot lock folder `" << location() << "':"
                 << strerror(rc) << "(" << rc << ")";
        mReadOnly = true;
        return rc;
      }
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "mutt_dotlock -p " + QFile::encodeName(KShell::quoteArg(indexLocation()));
        rc = system( cmd_str.data() );
        if( rc != 0 )
        {
          kDebug() << "Cannot lock index of folder `" << location() << "':"
                   << strerror(rc) << "(" << rc << ")";
          mReadOnly = true;
          return rc;
        }
      }
#endif
      break;

    case lock_none:
    default:
      break;
  }


  mFilesLocked = true;
#endif
  return 0;
}

//-------------------------------------------------------------
FolderJob*
KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
                           KMFolder *folder, const QString&, const AttachmentStrategy* ) const
{
  MboxJob *job = new MboxJob( msg, jt, folder );
  job->setParent( this );
  return job;
}

//-------------------------------------------------------------
FolderJob*
KMFolderMbox::doCreateJob( QList<KMMessage*>& msgList, const QString& sets,
                           FolderJob::JobType jt, KMFolder *folder ) const
{
  MboxJob *job = new MboxJob( msgList, sets, jt, folder );
  job->setParent( this );
  return job;
}

//-----------------------------------------------------------------------------
int KMFolderMbox::unlock()
{
#ifdef Q_WS_WIN
# ifdef __GNUC__
#  warning TODO implement mbox unlocking on Windows
# else
#  pragma WARNING( TODO implement mbox unlocking on Windows )
# endif
  mFilesLocked = false;
  return 0;
#else
  int rc;
  struct flock fl;
  fl.l_type=F_UNLCK;
  fl.l_whence=0;
  fl.l_start=0;
  fl.l_len=0;
  QByteArray cmd_str;

  assert(mStream != 0);
  mFilesLocked = false;

  switch( mLockType )
  {
    case FCNTL:
#ifdef KMAIL_SQLITE_INDEX
#else
      if (mIndexStream)
        fcntl(fileno(mIndexStream), F_SETLK, &fl);
#endif
      fcntl(fileno(mStream), F_SETLK, &fl);
      rc = errno;
      break;

    case procmail_lockfile:
      cmd_str = "rm -f ";
      if (!mProcmailLockFileName.isEmpty())
        cmd_str += QFile::encodeName(KShell::quoteArg(mProcmailLockFileName));
      else
        cmd_str += QFile::encodeName(KShell::quoteArg(location() + ".lock"));

      rc = system( cmd_str.data() );
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "rm -f " + QFile::encodeName(KShell::quoteArg(indexLocation() + ".lock"));
        rc = system( cmd_str.data() );
      }
#endif
      break;

    case mutt_dotlock:
      cmd_str = "mutt_dotlock -u " + QFile::encodeName(KShell::quoteArg(location()));
      rc = system( cmd_str.data() );
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "mutt_dotlock -u " + QFile::encodeName(KShell::quoteArg(indexLocation()));
        rc = system( cmd_str.data() );
      }
#endif
      break;

    case mutt_dotlock_privileged:
      cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KShell::quoteArg(location()));
      rc = system( cmd_str.data() );
#ifdef KMAIL_SQLITE_INDEX
#else
      if( mIndexStream )
      {
        cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KShell::quoteArg(indexLocation()));
        rc = system( cmd_str.data() );
      }
#endif
      break;

    case lock_none:
    default:
      rc = 0;
      break;
  }
  return rc;
#endif
}


//-----------------------------------------------------------------------------
KMFolderIndex::IndexStatus KMFolderMbox::indexStatus()
{
  QFileInfo contInfo(location());
  QFileInfo indInfo(indexLocation());

  if (!contInfo.exists()) return KMFolderIndex::IndexOk;
  if (!indInfo.exists()) return KMFolderIndex::IndexMissing;

  // Check whether the mbox file is more than 5 seconds newer than the index
  // file. The 5 seconds are added to reduce the number of false alerts due
  // to slightly out of sync clocks of the NFS server and the local machine.
  return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
      ? KMFolderIndex::IndexTooOld
      : KMFolderIndex::IndexOk;
}


//-----------------------------------------------------------------------------
int KMFolderMbox::createIndexFromContents()
{
  char line[MAX_LINE];
  char status[8], xstatus[8];
  QByteArray subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
  QByteArray replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
  QByteArray sizeServerStr, uidStr;
  QByteArray contentTypeStr, charset;
  bool atEof = false;
  bool inHeader = true;
  KMMsgInfo* mi;
  QString msgStr;
  QRegExp regexp(MSG_SEPERATOR_REGEX);
  int i, num, numStatus;
  short needStatus;

  assert(mStream != 0);
  rewind(mStream);

  mMsgList.clear();

  num     = -1;
  numStatus= 11;
  off_t offs = 0;
  size_t size = 0;
  dateStr = "";
  fromStr = "";
  toStr = "";
  subjStr = "";
  *status = '\0';
  *xstatus = '\0';
  xmarkStr = "";
  replyToIdStr = "";
  replyToAuxIdStr = "";
  referencesStr = "";
  msgIdStr = "";
  needStatus = 3;
  size_t sizeServer = 0;
  ulong uid = 0;


  while (!atEof)
  {
    off_t pos = KDE_ftell(mStream);
    if (!fgets(line, MAX_LINE, mStream)) atEof = true;

    if (atEof ||
        (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 &&
         regexp.indexIn(line) >= 0))
    {
      size = pos - offs;
      pos = KDE_ftell(mStream);

      if (num >= 0)
      {
        if (numStatus <= 0)
        {
          msgStr = i18np("Creating index file: one message done", "Creating index file: %1 messages done", num);
          emit statusMsg(msgStr);
          numStatus = 10;
        }

        if (size > 0)
        {
          msgIdStr = msgIdStr.trimmed();
          if( !msgIdStr.isEmpty() ) {
            int rightAngle;
            rightAngle = msgIdStr.indexOf( '>' );
            if( rightAngle != -1 )
              msgIdStr.truncate( rightAngle + 1 );
          }

          replyToIdStr = replyToIdStr.trimmed();
          if( !replyToIdStr.isEmpty() ) {
            int rightAngle;
            rightAngle = replyToIdStr.indexOf( '>' );
            if( rightAngle != -1 )
              replyToIdStr.truncate( rightAngle + 1 );
          }

          referencesStr = referencesStr.trimmed();
          if( !referencesStr.isEmpty() ) {
            int leftAngle, rightAngle;
            leftAngle = referencesStr.lastIndexOf( '<' );
            if( ( leftAngle != -1 )
                && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
              // use the last reference, instead of missing In-Reply-To
              replyToIdStr = referencesStr.mid( leftAngle );
            }

            // find second last reference
            leftAngle = referencesStr.lastIndexOf( '<', leftAngle - 1 );
            if( leftAngle != -1 )
              referencesStr = referencesStr.mid( leftAngle );
            rightAngle = referencesStr.lastIndexOf( '>' );
            if( rightAngle != -1 )
              referencesStr.truncate( rightAngle + 1 );

            // Store the second to last reference in the replyToAuxIdStr
            // It is a good candidate for threading the message below if the
            // message In-Reply-To points to is not kept in this folder,
            // but e.g. in an Outbox
            replyToAuxIdStr = referencesStr;
            rightAngle = referencesStr.indexOf( '>' );
            if( rightAngle != -1 )
              replyToAuxIdStr.truncate( rightAngle + 1 );
          }

          contentTypeStr = contentTypeStr.trimmed();
          charset = "";
          if ( !contentTypeStr.isEmpty() ) {
            int cidx = contentTypeStr.indexOf( "charset=" );
            if ( cidx != -1 ) {
              charset = contentTypeStr.mid( cidx + 8 );
              if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
                charset = charset.mid( 1 );
              }
              cidx = 0;
              while ( cidx < charset.length() ) {
                if ( charset[cidx] == '"' ||
                     ( !isalnum(charset[cidx]) &&
                       charset[cidx] != '-' && charset[cidx] != '_' ) ) {
                  break;
                }
                ++cidx;
              }
              charset.truncate( cidx );
            }
          }

          mi = new KMMsgInfo(folder());
          mi->init( subjStr.trimmed(),
                    fromStr.trimmed(),
                    toStr.trimmed(),
                    0, MessageStatus::statusNew(),
                    xmarkStr.trimmed(),
                    replyToIdStr, replyToAuxIdStr, msgIdStr,
                    KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
                    KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid );
          mi->setStatus(status, xstatus);
          mi->setDate( dateStr.trimmed().constData() );
          mi->setDirty(false);
          mMsgList.append(mi, mExportsSernums );

          *status = '\0';
          *xstatus = '\0';
          needStatus = 3;
          xmarkStr = "";
          replyToIdStr = "";
          replyToAuxIdStr = "";
          referencesStr = "";
          msgIdStr = "";
          dateStr = "";
          fromStr = "";
          subjStr = "";
          sizeServer = 0;
          uid = 0;
        }
        else num--,numStatus++;
      }

      offs = KDE_ftell(mStream);
      num++;
      numStatus--;
      inHeader = true;
      continue;
    }
    // Is this a long header line?
    if (inHeader && (line[0]=='\t' || line[0]==' '))
    {
      i = 0;
      while (line [i]=='\t' || line [i]==' ') i++;
      if (line [i] < ' ' && line [i]>0) inHeader = false;
      else if (lastStr) *lastStr += line + i;
    }
    else lastStr = 0;

    if (inHeader && (line [0]=='\n' || line [0]=='\r'))
      inHeader = false;
    if (!inHeader) continue;

    /* -sanders Make all messages read when auto-recreating index */
    /* Reverted, as it breaks reading the sent mail status, for example.
       -till */
    if ( ( needStatus & 1) && strncasecmp( line, "Status:", 7 ) == 0 )  {
      for ( i=0; i<4 && line[i+8] > ' '; ++i ) {
        status[i] = line[i+8];
      }
      status[i] = '\0';
      needStatus &= ~1;
    } else if ( ( needStatus & 2 ) &&
                strncasecmp( line, "X-Status:", 9 ) == 0 ) {
      for ( i=0; i<4 && line[i+10] > ' '; ++i ) {
        xstatus[i] = line[i+10];
      }
      xstatus[i] = '\0';
      needStatus &= ~2;
    } else if ( strncasecmp( line, "X-KMail-Mark:", 13 ) == 0 ) {
        xmarkStr = QByteArray( line + 13 );
    } else if ( strncasecmp( line, "In-Reply-To:", 12 ) == 0 ) {
      replyToIdStr = QByteArray( line + 12 );
      lastStr = &replyToIdStr;
    } else if ( strncasecmp( line, "References:", 11 ) == 0 ) {
      referencesStr = QByteArray( line + 11 );
      lastStr = &referencesStr;
    } else if ( strncasecmp( line, "Message-Id:", 11 ) == 0 ) {
      msgIdStr = QByteArray( line + 11 );
      lastStr = &msgIdStr;
    } else if ( strncasecmp( line, "Date:", 5 ) == 0 ) {
      dateStr = QByteArray( line + 5 );
      lastStr = &dateStr;
    } else if ( strncasecmp( line, "From:", 5 ) == 0 ) {
      fromStr = QByteArray( line + 5 );
      lastStr = &fromStr;
    } else if ( strncasecmp( line, "To:", 3 ) == 0 ) {
      toStr = QByteArray( line + 3 );
      lastStr = &toStr;
    } else if ( strncasecmp( line, "Subject:", 8 ) == 0 ) {
      subjStr = QByteArray( line + 8 );
      lastStr = &subjStr;
    } else if ( strncasecmp( line, "X-Length:", 9 ) == 0 ) {
      sizeServerStr = QByteArray( line + 9 );
      sizeServer = sizeServerStr.toULong();
      lastStr = &sizeServerStr;
    } else if ( strncasecmp( line, "X-UID:", 6 ) == 0 ) {
      uidStr = QByteArray( line + 6 );
      uid = uidStr.toULong();
      lastStr = &uidStr;
    } else if ( strncasecmp( line, "Content-Type:", 13 ) == 0 ) {
      contentTypeStr = QByteArray( line + 13 );
      lastStr = &contentTypeStr;
    }
  }

  if ( mAutoCreateIndex ) {
    emit statusMsg( i18n("Writing index file") );
    writeIndex();
  } else {
#ifdef KMAIL_SQLITE_INDEX
#else
    mHeaderOffset = 0;
#endif
  }

  correctUnreadMsgsCount();

  if ( kmkernel->outboxFolder() == folder() && count() > 0 ) {
    KMessageBox::queuedMessageBox(
      0, KMessageBox::Information,
      i18n("Your outbox contains messages which were "
           "most-likely not created by KMail;\n"
           "please remove them from there if you "
           "do not want KMail to send them.") );
  }

  invalidateFolder();
  return 0;
}


//-----------------------------------------------------------------------------
KMMessage* KMFolderMbox::readMsg(int idx)
{
  KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];

  assert(mi!=0 && !mi->isMessage());
  assert(mStream != 0);

  KMMessage *msg = new KMMessage(*mi); // note that mi is deleted by the line below
  mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
  msg->fromDwString(getDwString(idx));
  return msg;
}


#define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
// performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
static size_t unescapeFrom( char* str, size_t strLen ) {
  if ( !str )
    return 0;
  if ( strLen <= STRDIM(">From ") )
    return strLen;

  // yes, *d++ = *s++ is a no-op as long as d == s (until after the
  // first >From_), but writes are cheap compared to reads and the
  // data is already in the cache from the read, so special-casing
  // might even be slower...
  const char * s = str;
  char * d = str;
  const char * const e = str + strLen - STRDIM(">From ");

  while ( s < e ) {
    if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
      *d++ = *s++;  // == '\n'
      *d++ = *s++;  // == '>'
      while ( s < e && *s == '>' )
        *d++ = *s++;
      if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 )
        --d;
    }
    *d++ = *s++; // yes, s might be e here, but e is not the end :-)
  }
  // copy the rest:
  while ( s < str + strLen )
    *d++ = *s++;
  if ( d < s ) // only NUL-terminate if it's shorter
    *d = 0;

  return d - str;
}

//static
QByteArray KMFolderMbox::escapeFrom( const DwString & str ) {
  const unsigned int strLen = str.length();
  if ( strLen <= STRDIM("From ") )
    return KMail::Util::ByteArray(str);
  // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
  QByteArray result( int( strLen + 5 ) / 6 * 7 + 1, '\0' );

  const char * s = str.data();
  const char * const e = s + strLen - STRDIM("From ");
  char * d = result.data();

  bool onlyAnglesAfterLF = false; // dont' match ^From_
  while ( s < e ) {
    switch ( *s ) {
    case '\n':
      onlyAnglesAfterLF = true;
      break;
    case '>':
      break;
    case 'F':
      if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
        *d++ = '>';
      // fall through
    default:
      onlyAnglesAfterLF = false;
      break;
    }
    *d++ = *s++;
  }
  while ( s < str.data() + strLen )
    *d++ = *s++;

  result.truncate( d - result.data() );
  return result;
}

#undef STRDIM

//-----------------------------------------------------------------------------
DwString KMFolderMbox::getDwString(int idx)
{
  KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];

  assert(mi!=0);
  assert(mStream != 0);

  size_t msgSize = mi->msgSize();
  char* msgText = new char[ msgSize + 1 ];

  KDE_fseek(mStream, mi->folderOffset(), SEEK_SET);
  fread(msgText, msgSize, 1, mStream);
  msgText[msgSize] = '\0';

  size_t newMsgSize = unescapeFrom( msgText, msgSize );
  newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize );

  DwString msgStr;
  // the DwString takes possession of msgText, so we must not delete msgText
  msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
  return msgStr;
}


//-----------------------------------------------------------------------------
int KMFolderMbox::addMsg( KMMessage *aMsg, int *aIndex_ret )
{
  if ( !canAddMsgNow( aMsg, aIndex_ret ) ) {
    return 0;
  }

  KMFolderOpener openThis( folder(), "mboxaddMsg" );
  if ( openThis.openResult() )
  {
    kDebug() << openThis.openResult() << " of folder: " << label();
    return openThis.openResult();
  }

  // take message out of the folder it is currently in, if any
  KMFolder* msgParent = aMsg->parent();
  int idx = -1;
  if ( msgParent ) {
    if ( msgParent== folder() ) {
      if ( kmkernel->folderIsDraftOrOutbox( folder() ) ) {
        //special case for Edit message.
        kDebug() << "Editing message in outbox or drafts";
      } else {
        return 0;
      }
    }

    idx = msgParent->find( aMsg );
    msgParent->getMsg( idx );
  }

  if ( folderType() != KMFolderTypeImap ) {
/*
QFile fileD0( "testdat_xx-kmfoldermbox-0" );
if( fileD0.open( QIODevice::WriteOnly ) ) {
    QDataStream ds( &fileD0 );
    ds.writeRawData( aMsg->asString(), aMsg->asString().length() );
    fileD0.close();  // If data is 0 we just create a zero length file.
}
*/
    aMsg->setStatusFields();
/*
QFile fileD1( "testdat_xx-kmfoldermbox-1" );
if( fileD1.open( QIODevice::WriteOnly ) ) {
    QDataStream ds( &fileD1 );
    ds.writeRawData( aMsg->asString(), aMsg->asString().length() );
    fileD1.close();  // If data is 0 we just create a zero length file.
}
*/
    if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
      aMsg->removeHeaderField("Content-Type");        // the line above
  }
  QByteArray msgText = escapeFrom( aMsg->asDwString() );
  size_t len = msgText.size();

  assert( mStream != 0 );
  clearerr( mStream );
  if ( len <= 0 ) {
    kDebug() << "Message added to folder `" << objectName()
             << "' contains no data. Ignoring it.";
    return 0;
  }

  // Make sure the file is large enough to check for an end
  // character
  KDE_fseek( mStream, 0, SEEK_END );
  off_t revert = KDE_ftell( mStream );
  int growth = 0;
  if ( KDE_ftell( mStream ) >= 2 ) {
    // write message to folder file
    char endStr[3];
    KDE_fseek( mStream, -2, SEEK_END );
    fread( endStr, 1, 2, mStream ); // ensure separating empty line
    if ( KDE_ftell( mStream ) > 0 && endStr[0]!='\n' ) {
      ++growth;
      KDE_fseek( mStream, 0, SEEK_END ); // required at least on Windows, Solaris, etc.
      if ( endStr[1]!='\n' ) {
        //printf ("****endStr[1]=%c\n", endStr[1]);
        fwrite( "\n\n", 1, 2, mStream );
        ++growth;
      } else {
        fwrite( "\n", 1, 1, mStream );
      }
    }
  }
  KDE_fseek( mStream, 0, SEEK_END ); // this is needed on solaris and others
  int error = ferror( mStream );
  if ( error )
    return error;

  QByteArray messageSeparator( aMsg->mboxMessageSeparator() );
  fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream );
  off_t offs = KDE_ftell( mStream );
  fwrite( msgText.data(), len, 1, mStream );
  if ( msgText[(int)len-1] != '\n' ) {
    fwrite( "\n\n", 1, 2, mStream );
  }
  fflush( mStream );
  size_t size = KDE_ftell( mStream ) - offs;

  error = ferror( mStream );
  if ( error ) {
    kDebug() << "Error: Could not add message to folder:" << strerror(errno);
    if ( KDE_ftell( mStream ) > revert ) {
      kDebug() << "Undoing changes";
      truncate( QFile::encodeName(location()), revert );
    }
    kmkernel->emergencyExit( i18n("Could not add message to folder: ") +
                             QString::fromLocal8Bit( strerror( errno ) ) );

    /* This code is not 100% reliable
    bool busy = kmkernel->kbp()->isBusy();
    if (busy) kmkernel->kbp()->idle();
    KMessageBox::sorry(0,
          i18n("Unable to add message to folder.\n"
               "(No space left on device or insufficient quota?)\n"
               "Free space and sufficient quota are required to continue safely."));
    if (busy) kmkernel->kbp()->busy();
    kmkernel->kbp()->idle();
    */
    return error;
  }

  if ( msgParent ) {
    if ( idx >= 0 ) {
      msgParent->take( idx );
    }
  }
//  if (mAccount) aMsg->removeHeaderField("X-UID");

  if ( aMsg->status().isUnread() ||
       aMsg->status().isNew() ||
       (folder() == kmkernel->outboxFolder() ) ) {
    if ( mUnreadMsgs == -1 ) {
      mUnreadMsgs = 1;
    } else {
      ++mUnreadMsgs;
    }
    if ( !mQuiet ) {
      emit numUnreadMsgsChanged( folder() );
    }
  }
  ++mTotalMsgs;
  mCachedSize = -1;

  if ( aMsg->attachmentState() == KMMsgAttachmentUnknown &&
       aMsg->readyToShow() )
    aMsg->updateAttachmentState();

  // store information about the position in the folder file in the message
  aMsg->setParent( folder() );
  aMsg->setFolderOffset( offs );
  aMsg->setMsgSize( size );
  idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
  if ( aMsg->getMsgSerNum() <= 0 ) {
    aMsg->setMsgSerNum();
  } else {
    replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
  }

  // change the length of the previous message to encompass white space added
  if (( idx > 0) && (growth > 0) ) {
    // don't grow if a deleted message claims space at the end of the file
    if ( (ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) {
      mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
    }
  }

  // write index entry if desired
  if ( mAutoCreateIndex ) {
#ifdef KMAIL_SQLITE_INDEX
    // reset the db id, in case we have one, we are about to change folders
    // and can't reuse it there
    aMsg->setDbId( 0 );
#else
    assert( mIndexStream != 0 );
    clearerr( mIndexStream );
    KDE_fseek( mIndexStream, 0, SEEK_END );
    revert = KDE_ftell( mIndexStream );
#endif

    KMMsgBase * mb = &aMsg->toMsgBase();
    error = writeMessages( mb, true /*flush*/ );

    if ( mExportsSernums ) {
      error |= appendToFolderIdsFile( idx );
    }

    if (error) {
      kWarning() <<"Error: Could not add message to folder (No space left on device?)";
#ifdef KMAIL_SQLITE_INDEX
#else
      if ( KDE_ftell( mIndexStream ) > revert ) {
        kWarning() <<"Undoing changes";
        truncate( QFile::encodeName( indexLocation() ), revert );
      }
#endif
      if ( errno ) {
        kmkernel->emergencyExit( i18n("Could not add message to folder: ") +
                                 QString::fromLocal8Bit( strerror( errno ) ) );
      } else {
        kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
      }

      /* This code may not be 100% reliable
      bool busy = kmkernel->kbp()->isBusy();
      if (busy) kmkernel->kbp()->idle();
      KMessageBox::sorry(0,
        i18n("Unable to add message to folder.\n"
             "(No space left on device or insufficient quota?)\n"
             "Free space and sufficient quota are required to continue safely."));
      if (busy) kmkernel->kbp()->busy();
      */
      return error;
    }
  }

  if ( aIndex_ret ) {
    *aIndex_ret = idx;
  }
  emitMsgAddedSignals(idx);

  // All streams have been flushed without errors if we arrive here
  // Return success!
  // (Don't return status of stream, it may have been closed already.)
  return 0;
}

int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE *tmpfile,
                           off_t&offs, bool &done )
{
  int rc = 0;
  QByteArray mtext;
  unsigned int stopIndex = nbMessages == -1
                       ? mMsgList.count()
                       : qMin( mMsgList.count(), startIndex + nbMessages );
  //kDebug() << "KMFolderMbox: compacting from" << startIndex <<" to" << stopIndex;
  for ( unsigned int idx = startIndex; idx < stopIndex; ++idx ) {
    KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at( idx );
    size_t msize = mi->msgSize();
    if ( (size_t) mtext.size() < msize + 2 ) {
      mtext.resize( msize+2 );
    }
    off_t folder_offset = mi->folderOffset();

    //now we need to find the separator! grr...
    for( off_t i = folder_offset-25; true; i -= 20 ) {
      off_t chunk_offset = i <= 0 ? 0 : i;
      if ( KDE_fseek( mStream, chunk_offset, SEEK_SET ) == -1 ) {
        rc = errno;
        break;
      }
      if ( mtext.size() < 20 ) {
        mtext.resize( 20 );
      }
      fread( mtext.data(), 20, 1, mStream );
      if ( i <= 0 ) { //woops we've reached the top of the file, last try..
        if ( mtext.indexOf( "from " ) ) {
          if ( (off_t) mtext.size() < folder_offset ) {
              mtext.resize( folder_offset );
          }
          if ( KDE_fseek( mStream, chunk_offset, SEEK_SET) == -1 ||
               !fread( mtext.data(), folder_offset, 1, mStream ) ||
               !fwrite( mtext.data(), folder_offset, 1, tmpfile ) ) {
            rc = errno;
            break;
          }
          offs += folder_offset;
        } else {
          rc = 666; // yes.. this is evil
        }
        break;
      } else {
        int last_crlf = -1;
        for ( int i2 = 0; i2 < 20; i2++ ) {
          if ( *(mtext.data()+i2) == '\n' ) {
            last_crlf = i2;
          }
        }
        if ( last_crlf != -1 ) {
          int size = folder_offset - ( i + last_crlf + 1 );
          if ( (int)mtext.size() < size ) {
              mtext.resize( size );
          }
          if ( KDE_fseek( mStream, i + last_crlf+1, SEEK_SET ) == -1 ||
               !fread( mtext.data(), size, 1, mStream ) ||
               !fwrite( mtext.data(), size, 1, tmpfile ) ) {
            rc = errno;
            break;
          }
          offs += size;
          break;
        }
      }
    }
    if ( rc ) {
      break;
    }

    //now actually write the message
    if ( KDE_fseek( mStream, folder_offset, SEEK_SET ) == -1 ||
         !fread( mtext.data(), msize, 1, mStream ) ||
         !fwrite( mtext.data(), msize, 1, tmpfile ) ) {
      rc = errno;
      break;
    }
    mi->setFolderOffset( offs );
    offs += msize;
  }
  done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors
  emit compacted();
  return rc;
}

//-----------------------------------------------------------------------------
int KMFolderMbox::compact( bool silent )
{
  // This is called only when the user explicitly requests compaction,
  // so we don't check needsCompact.

  KMail::MboxCompactionJob *job =
    new KMail::MboxCompactionJob( folder(), true /*immediate*/ );
  int rc = job->executeNow( silent );
  // Note that job autodeletes itself.

  // If this is the current folder, the changed signal will ultimately call
  // KMHeaders::setFolderInfoStatus which will override the message,
  // so save/restore it
  QString statusMsg = BroadcastStatus::instance()->statusMsg();
  emit changed();
  emit compacted();
  BroadcastStatus::instance()->setStatusMsg( statusMsg );
  return rc;
}

//-----------------------------------------------------------------------------
void KMFolderMbox::setLockType( LockType ltype )
{
  mLockType = ltype;
}

//-----------------------------------------------------------------------------
void KMFolderMbox::setProcmailLockFileName( const QString &fname )
{
  mProcmailLockFileName = fname;
}

//-----------------------------------------------------------------------------
int KMFolderMbox::removeContents()
{
  int rc = 0;
  rc = unlink(QFile::encodeName(location()));
  return rc;
}

//-----------------------------------------------------------------------------
int KMFolderMbox::expungeContents()
{
  int rc = 0;
  if (truncate(QFile::encodeName(location()), 0))
    rc = errno;
  return rc;
}

//-----------------------------------------------------------------------------
/*virtual*/
qint64 KMFolderMbox::doFolderSize() const
{
  QFileInfo info( location() );
  return info.size();
}

//-----------------------------------------------------------------------------
#include "kmfoldermbox.moc"
