<commit_before>/*
    Copyright (c) 2009 <NAME> <<EMAIL>>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library 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 Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "mboxresource.h"

#include <akonadi/attributefactory.h>
#include <akonadi/changerecorder.h>
#include <akonadi/collectionfetchjob.h>
#include <akonadi/collectionmodifyjob.h>
#include <akonadi/itemfetchscope.h>
#include <kmime/kmime_message.h>
#include <KWindowSystem>
#include <QtDBus/QDBusConnection>

#include "deleteditemsattribute.h"
#include "lockmethodpage.h"
#include "mbox.h"
#include "settingsadaptor.h"
#include "singlefileresourceconfigdialog.h"

using namespace Akonadi;

static Entity::Id collectionId(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split(':').size() == 3);
  return remoteItemId.split(':').first().toLongLong();
}

static QString mboxFile(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split(':').size() == 3);
  return remoteItemId.split(':').at(1);
}

static quint64 itemOffset(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split(':').size() == 3);
  return remoteItemId.split(':').last().toULongLong();
}

MboxResource::MboxResource( const QString &id )
  : SingleFileResource<Settings>( id )
  , mMBox( 0 )
{
  new SettingsAdaptor( Settings::self() );
  QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ),
                              Settings::self(), QDBusConnection::ExportAdaptors );

  QStringList mimeTypes;
  mimeTypes << "message/rfc822";
  setSupportedMimetypes( mimeTypes, "message-rfc822" );

  // Register the list of deleted items as an attribute of the collection.
  AttributeFactory::registerAttribute<DeletedItemsAttribute>();
}

MboxResource::~MboxResource()
{
}

void MboxResource::configure( WId windowId )
{
  SingleFileResourceConfigDialog<Settings> dlg( windowId );
  dlg.addPage( "Lock method", new LockMethodPage() );
  dlg.setCaption( i18n("Select MBox file") );
  if ( dlg.exec() == QDialog::Accepted ) {
    reloadFile();
  }
}

void MboxResource::retrieveItems( const Akonadi::Collection &col )
{
  Q_UNUSED( col );
  if ( !mMBox )
    return;

  QList<MsgInfo> entryList;
  if ( col.hasAttribute<DeletedItemsAttribute>() ) {
    DeletedItemsAttribute *attr = col.attribute<DeletedItemsAttribute>();
    entryList = mMBox->entryList( attr->deletedItemOffsets() );
  } else { // No deleted items (yet)
    entryList = mMBox->entryList();
  }

  mMBox->lock(); // Lock the file so that it doesn't get locked for every
                 // readEntryHeaders() call.

  Item::List items;
  QString colId = QString::number( col.id() );
  QString colRid = col.remoteId();
  foreach ( const MsgInfo &entry, entryList ) {
    // TODO: Use cache policy to see what actualy has to been set as payload.
    //       Currently most views need a minimal amount of information so the
    //       Items get Envelopes as payload.
    KMime::Message *mail = new KMime::Message();
    mail->setHead( KMime::CRLFtoLF( mMBox->readEntryHeaders( entry.first ) ) );
    mail->parse();

    Item item;
    item.setRemoteId( colId + ':' + colRid + ':' + QString::number( entry.first ) );
    item.setMimeType( "message/rfc822" );
    item.setSize( entry.second );
    item.setPayload( MessagePtr( mail ) );
    items << item;
  }

  mMBox->unlock(); // Now we have the items, unlock

  itemsRetrieved( items );
}

bool MboxResource::retrieveItem( const Akonadi::Item &item, const QSet<QByteArray> &parts )
{
  Q_UNUSED(parts);

  if ( !mMBox ) {
    emit error( i18n( "MBox not loaded." ) );
    return false;
  }

  QString rid = item.remoteId();
  quint64 offset = itemOffset( rid );
  KMime::Message *mail = mMBox->readEntry( offset );
  if ( !mail ) {
    emit error( i18n("Failed to read message with uid '%1'.", rid ) );
    return false;
  }

  Item i( item );
  i.setPayload( MessagePtr( mail ) );
  itemRetrieved( i );
  return true;
}

void MboxResource::aboutToQuit()
{
}

void MboxResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection )
{
  if ( !mMBox ) {
    cancelTask( i18n( "MBox not loaded." ) );
    return;
  }

  // we can only deal with mail
  if ( !item.hasPayload<MessagePtr>() ) {
    cancelTask( i18n( "Only email messages can be added to the MBox resource." ) );
    return;
  }

  qint64 offset = mMBox->appendEntry( item.payload<MessagePtr>() );
  if ( offset == -1 ) {
    cancelTask( i18n( "Mail message not added to the MBox." ) );
    return;
  }

  const QString rid = QString::number( collection.id() ) + ':'
                      + collection.remoteId() + ':' + QString::number( offset );

  Item i( item );
  i.setRemoteId(rid);

  changeCommitted( i );
}

void MboxResource::itemChanged( const Akonadi::Item &item, const QSet<QByteArray> &parts )
{
  if ( parts.contains( "PLD:RFC822" ) ) {
    kDebug() << "MboxResource::itemChanged()" << itemOffset( item.remoteId() );
    // Only complete messages can be stored in a MBox file. Because all messages
    // are stored in one single file we do an ItemDelete and an ItemCreate to
    // prevent that whole file must been rewritten.
    CollectionFetchJob *fetchJob =
      new CollectionFetchJob( Collection( collectionId( item.remoteId() ) )
                              , CollectionFetchJob::Base );

    connect( fetchJob, SIGNAL( result( KJob* ) ),
             this, SLOT( onCollectionFetch( KJob* ) ) );

    mCurrentItemDeletions.insert( fetchJob, item );

    fetchJob->start();
    return;
  }

  changeProcessed();
}<commit_msg>Urls from the SingleFileResource can contain a ':' so change the rid seperator to "::" (Not sure if that's save enough).<commit_after>/*
    Copyright (c) 2009 <NAME> <<EMAIL>>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library 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 Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "mboxresource.h"

#include <akonadi/attributefactory.h>
#include <akonadi/changerecorder.h>
#include <akonadi/collectionfetchjob.h>
#include <akonadi/collectionmodifyjob.h>
#include <akonadi/itemfetchscope.h>
#include <kmime/kmime_message.h>
#include <KWindowSystem>
#include <QtDBus/QDBusConnection>

#include "deleteditemsattribute.h"
#include "lockmethodpage.h"
#include "mbox.h"
#include "settingsadaptor.h"
#include "singlefileresourceconfigdialog.h"

using namespace Akonadi;

static Entity::Id collectionId(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split("::").size() == 3);
  return remoteItemId.split("::").first().toLongLong();
}

static QString mboxFile(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split("::").size() == 3);
  return remoteItemId.split("::").at(1);
}

static quint64 itemOffset(const QString &remoteItemId)
{
  // [CollectionId]:[RemoteCollectionId]:[Offset]
  Q_ASSERT(remoteItemId.split("::").size() == 3);
  return remoteItemId.split("::").last().toULongLong();
}

MboxResource::MboxResource( const QString &id )
  : SingleFileResource<Settings>( id )
  , mMBox( 0 )
{
  new SettingsAdaptor( Settings::self() );
  QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ),
                              Settings::self(), QDBusConnection::ExportAdaptors );

  QStringList mimeTypes;
  mimeTypes << "message/rfc822";
  setSupportedMimetypes( mimeTypes, "message-rfc822" );

  // Register the list of deleted items as an attribute of the collection.
  AttributeFactory::registerAttribute<DeletedItemsAttribute>();
}

MboxResource::~MboxResource()
{
}

void MboxResource::configure( WId windowId )
{
  SingleFileResourceConfigDialog<Settings> dlg( windowId );
  dlg.addPage( "Lock method", new LockMethodPage() );
  dlg.setCaption( i18n("Select MBox file") );
  if ( dlg.exec() == QDialog::Accepted ) {
    reloadFile();
  }
}

void MboxResource::retrieveItems( const Akonadi::Collection &col )
{
  Q_UNUSED( col );
  if ( !mMBox )
    return;

  QList<MsgInfo> entryList;
  if ( col.hasAttribute<DeletedItemsAttribute>() ) {
    DeletedItemsAttribute *attr = col.attribute<DeletedItemsAttribute>();
    entryList = mMBox->entryList( attr->deletedItemOffsets() );
  } else { // No deleted items (yet)
    entryList = mMBox->entryList();
  }

  mMBox->lock(); // Lock the file so that it doesn't get locked for every
                 // readEntryHeaders() call.

  Item::List items;
  QString colId = QString::number( col.id() );
  QString colRid = col.remoteId();
  foreach ( const MsgInfo &entry, entryList ) {
    // TODO: Use cache policy to see what actualy has to been set as payload.
    //       Currently most views need a minimal amount of information so the
    //       Items get Envelopes as payload.
    KMime::Message *mail = new KMime::Message();
    mail->setHead( KMime::CRLFtoLF( mMBox->readEntryHeaders( entry.first ) ) );
    mail->parse();

    Item item;
    item.setRemoteId( colId + "::" + colRid + "::" + QString::number( entry.first ) );
    item.setMimeType( "message/rfc822" );
    item.setSize( entry.second );
    item.setPayload( MessagePtr( mail ) );
    items << item;
  }

  mMBox->unlock(); // Now we have the items, unlock

  itemsRetrieved( items );
}

bool MboxResource::retrieveItem( const Akonadi::Item &item, const QSet<QByteArray> &parts )
{
  Q_UNUSED(parts);

  if ( !mMBox ) {
    emit error( i18n( "MBox not loaded." ) );
    return false;
  }

  QString rid = item.remoteId();
  quint64 offset = itemOffset( rid );
  KMime::Message *mail = mMBox->readEntry( offset );
  if ( !mail ) {
    emit error( i18n("Failed to read message with uid '%1'.", rid ) );
    return false;
  }

  Item i( item );
  i.setPayload( MessagePtr( mail ) );
  itemRetrieved( i );
  return true;
}

void MboxResource::aboutToQuit()
{
}

void MboxResource::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection )
{
  if ( !mMBox ) {
    cancelTask( i18n( "MBox not loaded." ) );
    return;
  }

  // we can only deal with mail
  if ( !item.hasPayload<MessagePtr>() ) {
    cancelTask( i18n( "Only email messages can be added to the MBox resource." ) );
    return;
  }

  qint64 offset = mMBox->appendEntry( item.payload<MessagePtr>() );
  if ( offset == -1 ) {
    cancelTask( i18n( "Mail message not added to the MBox." ) );
    return;
  }

  const QString rid = QString::number( collection.id() ) + "::"
                      + collection.remoteId() + "::" + QString::number( offset );

  Item i( item );
  i.setRemoteId(rid);

  changeCommitted( i );
}

void MboxResource::itemChanged( const Akonadi::Item &item, const QSet<QByteArray> &parts )
{
  if ( parts.contains( "PLD:RFC822" ) ) {
    kDebug() << "MboxResource::itemChanged()" << itemOffset( item.remoteId() );
    // Only complete messages can be stored in a MBox file. Because all messages
    // are stored in one single file we do an ItemDelete and an ItemCreate to
    // prevent that whole file must been rewritten.
    CollectionFetchJob *fetchJob =
      new CollectionFetchJob( Collection( collectionId( item.remoteId() ) )
                              , CollectionFetchJob::Base );

    connect( fetchJob, SIGNAL( result( KJob* ) ),
             this, SLOT( onCollectionFetch( KJob* ) ) );

    mCurrentItemDeletions.insert( fetchJob, item );

    fetchJob->start();
    return;
  }

  changeProcessed();
}
{'fraction_non_alphanumeric': 0.1288937465844328, 'fraction_numerical': 0.008431571551253025, 'mean_word_length': 3.6957478005865103, 'pattern_counts': {'":': 11, '<': 61, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '353418', 'n_tokens_mistral': 4107, 'n_tokens_neox': 3970, 'n_words': 1136}