You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1142 lines
34 KiB
1142 lines
34 KiB
/*
|
|
This file is part of KMail, the KDE mail client.
|
|
Copyright (c) 2000 Don Sanders <sanders@kde.org>
|
|
|
|
KMail is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License, version 2, as
|
|
published by the Free Software Foundation.
|
|
|
|
KMail 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
|
|
*/
|
|
|
|
//Factor byteswap stuff into one header file
|
|
|
|
#include <config.h>
|
|
|
|
#include "kmfoldersearch.h"
|
|
#include "kmfolderimap.h"
|
|
#include "kmfoldermgr.h"
|
|
#include "kmsearchpattern.h"
|
|
#include "kmmsgdict.h"
|
|
#include "index.h"
|
|
#include "jobscheduler.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
#include <kconfig.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/file.h>
|
|
#include <utime.h>
|
|
|
|
#include <qfile.h>
|
|
|
|
#ifdef HAVE_BYTESWAP_H
|
|
#include <byteswap.h>
|
|
#endif
|
|
|
|
// We define functions as kmail_swap_NN so that we don't get compile errors
|
|
// on platforms where bswap_NN happens to be a function instead of a define.
|
|
|
|
/* Swap bytes in 32 bit value. */
|
|
#ifndef kmail_swap_32
|
|
#ifdef bswap_32
|
|
#define kmail_swap_32(x) bswap_32(x)
|
|
#else
|
|
#define kmail_swap_32(x) \
|
|
((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
|
|
(((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
|
|
#endif
|
|
#endif // kmail_swap_32
|
|
|
|
// Current version of the .index.search files
|
|
#define IDS_SEARCH_VERSION 1000
|
|
// The asterisk at the end is important
|
|
#define IDS_SEARCH_HEADER "# KMail-Search-IDs V%d\n*"
|
|
#define IDS_SEARCH_HEADER_LEN 30
|
|
|
|
|
|
KMSearch::KMSearch(QObject * parent, const char * name)
|
|
:QObject(parent, name)
|
|
{
|
|
mRemainingFolders = -1;
|
|
mRecursive = true;
|
|
mRunByIndex = mRunning = false;
|
|
mRoot = 0;
|
|
mSearchPattern = 0;
|
|
mFoundCount = 0;
|
|
mSearchCount = 0;
|
|
|
|
mProcessNextBatchTimer = new QTimer(0, "mProcessNextBatchTimer");
|
|
connect(mProcessNextBatchTimer, SIGNAL(timeout()), this, SLOT(slotProcessNextBatch()));
|
|
}
|
|
|
|
KMSearch::~KMSearch()
|
|
{
|
|
delete mProcessNextBatchTimer;
|
|
delete mSearchPattern;
|
|
}
|
|
|
|
bool KMSearch::write(QString location) const
|
|
{
|
|
KConfig config(location);
|
|
config.setGroup("Search Folder");
|
|
if (mSearchPattern)
|
|
mSearchPattern->writeConfig(&config);
|
|
if (mRoot.isNull())
|
|
config.writeEntry("Base", "");
|
|
else
|
|
config.writeEntry("Base", mRoot->idString());
|
|
config.writeEntry("Recursive", recursive());
|
|
return true;
|
|
}
|
|
|
|
bool KMSearch::read(QString location)
|
|
{
|
|
KConfig config( location );
|
|
config.setGroup( "Search Folder" );
|
|
if ( !mSearchPattern )
|
|
mSearchPattern = new KMSearchPattern();
|
|
mSearchPattern->readConfig( &config );
|
|
QString rootString = config.readEntry( "Base" );
|
|
mRoot = kmkernel->findFolderById( rootString );
|
|
mRecursive = config.readBoolEntry( "Recursive" );
|
|
return true;
|
|
}
|
|
|
|
void KMSearch::setSearchPattern(KMSearchPattern *searchPattern)
|
|
{
|
|
if ( running() )
|
|
stop();
|
|
if ( mSearchPattern != searchPattern ) {
|
|
delete mSearchPattern;
|
|
mSearchPattern = searchPattern;
|
|
}
|
|
}
|
|
|
|
bool KMSearch::inScope(KMFolder* folder) const
|
|
{
|
|
if ( mRoot.isNull() || folder == mRoot )
|
|
return true;
|
|
if ( !recursive() )
|
|
return false;
|
|
|
|
KMFolderDir *rootDir = mRoot->child();
|
|
KMFolderDir *ancestorDir = folder->parent();
|
|
while ( ancestorDir ) {
|
|
if ( ancestorDir == rootDir )
|
|
return true;
|
|
ancestorDir = ancestorDir->parent();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void KMSearch::start()
|
|
{
|
|
//close all referenced folders
|
|
QValueListIterator<QGuardedPtr<KMFolder> > fit;
|
|
for (fit = mOpenedFolders.begin(); fit != mOpenedFolders.end(); ++fit) {
|
|
if (!(*fit))
|
|
continue;
|
|
(*fit)->close( "kmsearch" );
|
|
}
|
|
mOpenedFolders.clear();
|
|
mFolders.clear();
|
|
|
|
if ( running() )
|
|
return;
|
|
|
|
if ( !mSearchPattern ) {
|
|
emit finished(true);
|
|
return;
|
|
}
|
|
|
|
mFoundCount = 0;
|
|
mSearchCount = 0;
|
|
mRunning = true;
|
|
mRunByIndex = false;
|
|
// check if this query can be done with the index
|
|
if ( kmkernel->msgIndex() && kmkernel->msgIndex()->startQuery( this ) ) {
|
|
mRunByIndex = true;
|
|
return;
|
|
}
|
|
|
|
mFolders.append( mRoot );
|
|
if ( recursive() )
|
|
{
|
|
//Append all descendants to folders
|
|
KMFolderNode* node;
|
|
KMFolder* folder;
|
|
QValueListConstIterator<QGuardedPtr<KMFolder> > it;
|
|
for ( it = mFolders.begin(); it != mFolders.end(); ++it )
|
|
{
|
|
folder = *it;
|
|
KMFolderDir *dir = 0;
|
|
if ( folder )
|
|
dir = folder->child();
|
|
else
|
|
dir = &kmkernel->folderMgr()->dir();
|
|
if ( !dir )
|
|
continue;
|
|
QPtrListIterator<KMFolderNode> it(*dir);
|
|
while ( (node = it.current()) ) {
|
|
++it;
|
|
if ( !node->isDir() ) {
|
|
KMFolder* kmf = dynamic_cast<KMFolder*>( node );
|
|
if ( kmf )
|
|
mFolders.append( kmf );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mRemainingFolders = mFolders.count();
|
|
mLastFolder = QString::null;
|
|
mProcessNextBatchTimer->start( 0, true );
|
|
}
|
|
|
|
void KMSearch::stop()
|
|
{
|
|
if ( !running() )
|
|
return;
|
|
if ( mRunByIndex ) {
|
|
if ( kmkernel->msgIndex() )
|
|
kmkernel->msgIndex()->stopQuery( this );
|
|
} else {
|
|
mIncompleteFolders.clear();
|
|
QValueListConstIterator<QGuardedPtr<KMFolder> > jt;
|
|
for ( jt = mOpenedFolders.begin(); jt != mOpenedFolders.end(); ++jt ) {
|
|
KMFolder *folder = *jt;
|
|
if ( !folder )
|
|
continue;
|
|
// explicitely stop jobs for this folder as it will not be closed below
|
|
// when the folder is currently selected
|
|
if ( folder->folderType() == KMFolderTypeImap ) {
|
|
KMAcctImap *account =
|
|
static_cast<KMFolderImap*>( folder->storage() )->account();
|
|
account->ignoreJobsForFolder( folder );
|
|
}
|
|
folder->storage()->search( 0 );
|
|
mSearchCount += folder->count();
|
|
folder->close("kmsearch");
|
|
}
|
|
}
|
|
mRemainingFolders = -1;
|
|
mOpenedFolders.clear();
|
|
mFolders.clear();
|
|
mLastFolder = QString::null;
|
|
mRunByIndex = mRunning = false;
|
|
emit finished(false);
|
|
}
|
|
|
|
void KMSearch::indexFinished() {
|
|
mRunning = false;
|
|
mRunByIndex = false;
|
|
}
|
|
|
|
void KMSearch::slotProcessNextBatch()
|
|
{
|
|
if ( !running() )
|
|
return;
|
|
|
|
if ( mFolders.count() != 0 )
|
|
{
|
|
KMFolder *folder = *( mFolders.begin() );
|
|
mFolders.erase( mFolders.begin() );
|
|
if ( folder )
|
|
{
|
|
mLastFolder = folder->label();
|
|
folder->open("kmsearch");
|
|
mOpenedFolders.append( folder );
|
|
connect( folder->storage(),
|
|
SIGNAL( searchResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ),
|
|
this,
|
|
SLOT( slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ) );
|
|
folder->storage()->search( mSearchPattern );
|
|
} else
|
|
--mRemainingFolders;
|
|
mProcessNextBatchTimer->start( 0, true );
|
|
return;
|
|
}
|
|
}
|
|
|
|
void KMSearch::slotSearchFolderResult( KMFolder* folder,
|
|
QValueList<Q_UINT32> serNums,
|
|
const KMSearchPattern* pattern,
|
|
bool complete )
|
|
{
|
|
if ( pattern != mSearchPattern )
|
|
return;
|
|
kdDebug(5006) << k_funcinfo << folder->label() << " found " << serNums.count() << endl;
|
|
mLastFolder = folder->label();
|
|
QValueListIterator<Q_UINT32> it;
|
|
for ( it = serNums.begin(); it != serNums.end(); ++it )
|
|
{
|
|
emit found( *it );
|
|
++mFoundCount;
|
|
}
|
|
if ( complete )
|
|
{
|
|
disconnect( folder->storage(),
|
|
SIGNAL( searchResult( KMFolder*, QValueList<Q_UINT32>,
|
|
const KMSearchPattern*, bool ) ),
|
|
this,
|
|
SLOT( slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>,
|
|
const KMSearchPattern*, bool ) ) );
|
|
--mRemainingFolders;
|
|
mSearchCount += folder->count();
|
|
folder->close("kmsearch");
|
|
mOpenedFolders.remove( folder );
|
|
if ( mRemainingFolders <= 0 )
|
|
{
|
|
mRemainingFolders = 0;
|
|
mRunning = false;
|
|
mLastFolder = QString::null;
|
|
mRemainingFolders = -1;
|
|
mFolders.clear();
|
|
emit finished( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMFolderSearch::KMFolderSearch(KMFolder* folder, const char* name)
|
|
: FolderStorage(folder, name)
|
|
{
|
|
mIdsStream = 0;
|
|
mSearch = 0;
|
|
mInvalid = false;
|
|
mUnlinked = true;
|
|
mTempOpened = false;
|
|
setNoChildren(true);
|
|
|
|
//Hook up some slots for live updating of search folders
|
|
//TODO: Optimize folderInvalidated, folderAdded, folderRemoved
|
|
connect(kmkernel->folderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
|
|
this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(folderAdded(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(folderRemoved(KMFolder*)),
|
|
this, SLOT(examineRemovedFolder(KMFolder*)));
|
|
connect(kmkernel->folderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
|
|
this, SLOT(propagateHeaderChanged(KMFolder*,int)));
|
|
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
|
|
this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(folderAdded(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
|
|
this, SLOT(examineRemovedFolder(KMFolder*)));
|
|
connect(kmkernel->imapFolderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
|
|
this, SLOT(propagateHeaderChanged(KMFolder*,int)));
|
|
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
|
|
this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
|
|
this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(folderAdded(KMFolder*)),
|
|
this, SLOT(examineInvalidatedFolder(KMFolder*)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
|
|
this, SLOT(examineRemovedFolder(KMFolder*)));
|
|
connect(kmkernel->dimapFolderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
|
|
this, SLOT(propagateHeaderChanged(KMFolder*,int)));
|
|
|
|
mExecuteSearchTimer = new QTimer(0, "mExecuteSearchTimer");
|
|
connect(mExecuteSearchTimer, SIGNAL(timeout()),
|
|
this, SLOT(executeSearch()));
|
|
}
|
|
|
|
KMFolderSearch::~KMFolderSearch()
|
|
{
|
|
delete mExecuteSearchTimer;
|
|
delete mSearch;
|
|
mSearch = 0;
|
|
if (mOpenCount > 0)
|
|
close("~foldersearch", TRUE);
|
|
}
|
|
|
|
void KMFolderSearch::setSearch(KMSearch *search)
|
|
{
|
|
truncateIndex(); //new search old index is obsolete
|
|
emit cleared();
|
|
mInvalid = false;
|
|
setDirty( true ); //have to write the index
|
|
if (!mUnlinked) {
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
mUnlinked = true;
|
|
}
|
|
if (mSearch != search) {
|
|
mSearch->stop();
|
|
delete mSearch;
|
|
mSearch = search; // take ownership
|
|
if (mSearch) {
|
|
QObject::connect(search, SIGNAL(found(Q_UINT32)),
|
|
SLOT(addSerNum(Q_UINT32)));
|
|
QObject::connect(search, SIGNAL(finished(bool)),
|
|
SLOT(searchFinished(bool)));
|
|
}
|
|
}
|
|
if (mSearch)
|
|
mSearch->write(location());
|
|
clearIndex();
|
|
mTotalMsgs = 0;
|
|
mUnreadMsgs = 0;
|
|
emit numUnreadMsgsChanged( folder() );
|
|
emit changed(); // really want a kmfolder cleared signal
|
|
/* TODO There is KMFolder::cleared signal now. Adjust. */
|
|
if (mSearch)
|
|
mSearch->start();
|
|
open("foldersearch"); // will be closed in searchFinished
|
|
}
|
|
|
|
void KMFolderSearch::executeSearch()
|
|
{
|
|
if (mSearch)
|
|
mSearch->stop();
|
|
setSearch(mSearch);
|
|
invalidateFolder();
|
|
}
|
|
|
|
const KMSearch* KMFolderSearch::search() const
|
|
{
|
|
return mSearch;
|
|
}
|
|
|
|
void KMFolderSearch::searchFinished(bool success)
|
|
{
|
|
if (!success)
|
|
mSerNums.clear();
|
|
close("foldersearch");
|
|
}
|
|
|
|
void KMFolderSearch::addSerNum(Q_UINT32 serNum)
|
|
{
|
|
if (mInvalid) // A new search is scheduled don't bother doing anything
|
|
return;
|
|
int idx = -1;
|
|
KMFolder *aFolder = 0;
|
|
KMMsgDict::instance()->getLocation(serNum, &aFolder, &idx);
|
|
// warn instead of assert() because of
|
|
// https://intevation.de/roundup/kolab/issue2216
|
|
if (!aFolder || (idx == -1)) {
|
|
kdDebug(5006) << "Not adding message with serNum " << serNum
|
|
<< ": folder is " << aFolder << ", index is " << idx << endl;
|
|
return;
|
|
}
|
|
if(mFolders.findIndex(aFolder) == -1) {
|
|
aFolder->open("foldersearch");
|
|
mFolders.append(aFolder);
|
|
}
|
|
setDirty( true ); //TODO append a single entry to .ids file and sync.
|
|
if (!mUnlinked) {
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
mUnlinked = true;
|
|
}
|
|
mSerNums.append(serNum);
|
|
KMMsgBase *mb = aFolder->getMsgBase(idx);
|
|
if (mb && (mb->isUnread() || mb->isNew())) {
|
|
if (mUnreadMsgs == -1)
|
|
mUnreadMsgs = 0;
|
|
++mUnreadMsgs;
|
|
emit numUnreadMsgsChanged( folder() );
|
|
}
|
|
emitMsgAddedSignals(mSerNums.count()-1);
|
|
}
|
|
|
|
void KMFolderSearch::removeSerNum(Q_UINT32 serNum)
|
|
{
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
int i = 0;
|
|
for(it = mSerNums.begin(); it != mSerNums.end(); ++it, ++i)
|
|
if ((*it) == serNum) {
|
|
int idx = -1;
|
|
KMFolder *aFolder = 0;
|
|
KMMsgDict::instance()->getLocation(serNum, &aFolder, &idx);
|
|
assert(aFolder && (idx != -1));
|
|
emit msgRemoved(folder(), serNum);
|
|
removeMsg(i);
|
|
return;
|
|
}
|
|
if (!mUnlinked) {
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
mUnlinked = true;
|
|
}
|
|
}
|
|
|
|
int KMFolderSearch::addMsg(KMMessage*, int* index_return)
|
|
{
|
|
//Not supported search folder can't own messages
|
|
*index_return = -1;
|
|
return 0;
|
|
}
|
|
|
|
bool KMFolderSearch::readSearch()
|
|
{
|
|
mSearch = new KMSearch;
|
|
QObject::connect(mSearch, SIGNAL(found(Q_UINT32)), SLOT(addSerNum(Q_UINT32)));
|
|
QObject::connect(mSearch, SIGNAL(finished(bool)), SLOT(searchFinished(bool)));
|
|
return mSearch->read(location());
|
|
}
|
|
|
|
int KMFolderSearch::open(const char *)
|
|
{
|
|
mOpenCount++;
|
|
kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
|
|
if (mOpenCount > 1)
|
|
return 0; // already open
|
|
|
|
readConfig();
|
|
if (!mSearch && !readSearch())
|
|
return -1;
|
|
|
|
emit cleared();
|
|
if (!mSearch || !search()->running())
|
|
if (!readIndex()) {
|
|
executeSearch();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int KMFolderSearch::canAccess()
|
|
{
|
|
assert(!folder()->name().isEmpty());
|
|
|
|
if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
void KMFolderSearch::sync()
|
|
{
|
|
if (mDirty) {
|
|
if (mSearch)
|
|
mSearch->write(location());
|
|
updateIndex();
|
|
}
|
|
}
|
|
|
|
void KMFolderSearch::reallyDoClose(const char* owner)
|
|
{
|
|
if (mAutoCreateIndex) {
|
|
if (mSearch)
|
|
mSearch->write(location());
|
|
updateIndex();
|
|
if (mSearch && search()->running())
|
|
mSearch->stop();
|
|
writeConfig();
|
|
}
|
|
|
|
//close all referenced folders
|
|
QValueListIterator<QGuardedPtr<KMFolder> > fit;
|
|
for (fit = mFolders.begin(); fit != mFolders.end(); ++fit) {
|
|
if (!(*fit))
|
|
continue;
|
|
(*fit)->close("foldersearch");
|
|
}
|
|
mFolders.clear();
|
|
|
|
clearIndex(TRUE);
|
|
|
|
if (mIdsStream)
|
|
fclose(mIdsStream);
|
|
|
|
mOpenCount = 0;
|
|
mIdsStream = 0;
|
|
mUnreadMsgs = -1;
|
|
}
|
|
|
|
int KMFolderSearch::create()
|
|
{
|
|
int old_umask;
|
|
int rc = unlink(QFile::encodeName(location()));
|
|
if (!rc)
|
|
return rc;
|
|
rc = 0;
|
|
|
|
assert(!folder()->name().isEmpty());
|
|
assert(mOpenCount == 0);
|
|
|
|
kdDebug(5006) << "Creating folder " << location() << endl;
|
|
if (access(QFile::encodeName(location()), F_OK) == 0) {
|
|
kdDebug(5006) << "KMFolderSearch::create call to access function failed."
|
|
<< endl;
|
|
return EEXIST;
|
|
}
|
|
|
|
old_umask = umask(077);
|
|
FILE *mStream = fopen(QFile::encodeName(location()), "w+");
|
|
umask(old_umask);
|
|
if (!mStream) return errno;
|
|
fclose(mStream);
|
|
|
|
clearIndex();
|
|
if (!mSearch) {
|
|
mSearch = new KMSearch();
|
|
QObject::connect(mSearch, SIGNAL(found(Q_UINT32)), SLOT(addSerNum(Q_UINT32)));
|
|
QObject::connect(mSearch, SIGNAL(finished(bool)), SLOT(searchFinished(bool)));
|
|
}
|
|
mSearch->write(location());
|
|
mOpenCount++;
|
|
mChanged = false;
|
|
mUnreadMsgs = 0;
|
|
mTotalMsgs = 0;
|
|
return rc;
|
|
}
|
|
|
|
int KMFolderSearch::compact( bool )
|
|
{
|
|
needsCompact = false;
|
|
return 0;
|
|
}
|
|
|
|
bool KMFolderSearch::isReadOnly() const
|
|
{
|
|
return false; //TODO: Make it true and get that working ok
|
|
}
|
|
|
|
FolderJob* KMFolderSearch::doCreateJob(KMMessage*, FolderJob::JobType,
|
|
KMFolder*, QString, const AttachmentStrategy* ) const
|
|
{
|
|
// Should never be called
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
FolderJob* KMFolderSearch::doCreateJob(QPtrList<KMMessage>&, const QString&,
|
|
FolderJob::JobType, KMFolder*) const
|
|
{
|
|
// Should never be called
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
const KMMsgBase* KMFolderSearch::getMsgBase(int idx) const
|
|
{
|
|
int folderIdx = -1;
|
|
KMFolder *folder = 0;
|
|
if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
|
|
return 0;
|
|
KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
|
|
assert(folder && (folderIdx != -1));
|
|
return folder->getMsgBase(folderIdx);
|
|
}
|
|
|
|
KMMsgBase* KMFolderSearch::getMsgBase(int idx)
|
|
{
|
|
int folderIdx = -1;
|
|
KMFolder *folder = 0;
|
|
if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
|
|
return 0;
|
|
KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
|
|
if (!folder || folderIdx == -1)
|
|
return 0; //exceptional case
|
|
return folder->getMsgBase(folderIdx);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMMessage* KMFolderSearch::getMsg(int idx)
|
|
{
|
|
int folderIdx = -1;
|
|
KMFolder *folder = 0;
|
|
if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
|
|
return 0;
|
|
KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
|
|
assert(folder && (folderIdx != -1));
|
|
KMMessage* msg = folder->getMsg( folderIdx );
|
|
return msg;
|
|
}
|
|
|
|
//-------------------------------------------------------------
|
|
void
|
|
KMFolderSearch::ignoreJobsForMessage( KMMessage* msg )
|
|
{
|
|
if ( !msg || msg->transferInProgress() )
|
|
return;
|
|
/* While non-imap folders manage their jobs themselves, imap ones let
|
|
their account manage them. Therefor first clear the jobs managed by
|
|
this folder via the inherited method, then clear the imap ones. */
|
|
FolderStorage::ignoreJobsForMessage( msg );
|
|
|
|
if (msg->parent()->folderType() == KMFolderTypeImap) {
|
|
KMAcctImap *account =
|
|
static_cast<KMFolderImap*>( msg->storage() )->account();
|
|
if( !account )
|
|
return;
|
|
account->ignoreJobsForMessage( msg );
|
|
}
|
|
}
|
|
|
|
|
|
int KMFolderSearch::find(const KMMsgBase* msg) const
|
|
{
|
|
int pos = 0;
|
|
Q_UINT32 serNum = msg->getMsgSerNum();
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
|
|
if ((*it) == serNum)
|
|
return pos;
|
|
++pos;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
QString KMFolderSearch::indexLocation() const
|
|
{
|
|
QString sLocation(folder()->path());
|
|
|
|
if (!sLocation.isEmpty()) sLocation += '/';
|
|
sLocation += '.';
|
|
sLocation += dotEscape(fileName());
|
|
sLocation += ".index";
|
|
sLocation += ".search";
|
|
|
|
return sLocation;
|
|
}
|
|
|
|
int KMFolderSearch::updateIndex()
|
|
{
|
|
if (mSearch && search()->running())
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
else
|
|
if (dirty())
|
|
return writeIndex();
|
|
return 0;
|
|
}
|
|
|
|
int KMFolderSearch::writeIndex( bool )
|
|
{
|
|
// TODO:If we fail to write the index we should panic the kernel
|
|
// TODO:and the same for other folder types too, and the msgDict.
|
|
QString filename = indexLocation();
|
|
int old_umask = umask(077);
|
|
QString tempName = filename + ".temp";
|
|
unlink(QFile::encodeName(tempName));
|
|
|
|
// We touch the folder, otherwise the index is regenerated, if KMail is
|
|
// running, while the clock switches from daylight savings time to normal time
|
|
utime(QFile::encodeName(location()), 0);
|
|
|
|
FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w");
|
|
umask(old_umask);
|
|
|
|
if (!tmpIndexStream) {
|
|
kdDebug(5006) << "Cannot write '" << filename
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
truncate(QFile::encodeName(filename), 0);
|
|
return -1;
|
|
}
|
|
fprintf(tmpIndexStream, IDS_SEARCH_HEADER, IDS_SEARCH_VERSION);
|
|
Q_UINT32 byteOrder = 0x12345678;
|
|
fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
|
|
|
|
Q_UINT32 count = mSerNums.count();
|
|
if (!fwrite(&count, sizeof(count), 1, tmpIndexStream)) {
|
|
fclose(tmpIndexStream);
|
|
truncate(QFile::encodeName(filename), 0);
|
|
return -1;
|
|
}
|
|
|
|
QValueVector<Q_UINT32>::iterator it;
|
|
for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
|
|
Q_UINT32 serNum = *it;
|
|
if (!fwrite(&serNum, sizeof(serNum), 1, tmpIndexStream))
|
|
return -1;
|
|
}
|
|
if (ferror(tmpIndexStream)) return ferror(tmpIndexStream);
|
|
if (fflush(tmpIndexStream) != 0) return errno;
|
|
if (fsync(fileno(tmpIndexStream)) != 0) return errno;
|
|
if (fclose(tmpIndexStream) != 0) return errno;
|
|
|
|
::rename(QFile::encodeName(tempName), QFile::encodeName(indexLocation()));
|
|
mDirty = FALSE;
|
|
mUnlinked = FALSE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DwString KMFolderSearch::getDwString(int idx)
|
|
{
|
|
return getMsgBase(idx)->parent()->getDwString( idx );
|
|
}
|
|
|
|
KMMessage* KMFolderSearch::readMsg(int idx)
|
|
{
|
|
int folderIdx = -1;
|
|
KMFolder *folder = 0;
|
|
KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
|
|
assert(folder && (folderIdx != -1));
|
|
return folder->getMsg( folderIdx );
|
|
}
|
|
|
|
bool KMFolderSearch::readIndex()
|
|
{
|
|
clearIndex();
|
|
QString filename = indexLocation();
|
|
mIdsStream = fopen(QFile::encodeName(filename), "r+");
|
|
if (!mIdsStream)
|
|
return false;
|
|
|
|
int version = 0;
|
|
fscanf(mIdsStream, IDS_SEARCH_HEADER, &version);
|
|
if (version != IDS_SEARCH_VERSION) {
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
return false;
|
|
}
|
|
bool swapByteOrder;
|
|
Q_UINT32 byte_order;
|
|
if (!fread(&byte_order, sizeof(byte_order), 1, mIdsStream)) {
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
return false;
|
|
}
|
|
swapByteOrder = (byte_order == 0x78563412);
|
|
|
|
Q_UINT32 count;
|
|
if (!fread(&count, sizeof(count), 1, mIdsStream)) {
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
return false;
|
|
}
|
|
if (swapByteOrder)
|
|
count = kmail_swap_32(count);
|
|
|
|
mUnreadMsgs = 0;
|
|
mSerNums.reserve(count);
|
|
for (unsigned int index = 0; index < count; index++) {
|
|
Q_UINT32 serNum;
|
|
int folderIdx = -1;
|
|
KMFolder *folder = 0;
|
|
bool readOk = fread(&serNum, sizeof(serNum), 1, mIdsStream);
|
|
if (!readOk) {
|
|
clearIndex();
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
return false;
|
|
}
|
|
if (swapByteOrder)
|
|
serNum = kmail_swap_32(serNum);
|
|
|
|
KMMsgDict::instance()->getLocation( serNum, &folder, &folderIdx );
|
|
if (!folder || (folderIdx == -1)) {
|
|
clearIndex();
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
return false;
|
|
}
|
|
mSerNums.push_back(serNum);
|
|
if(mFolders.findIndex(folder) == -1) {
|
|
if (mInvalid) //exceptional case for when folder has invalid ids
|
|
return false;
|
|
folder->open("foldersearch");
|
|
mFolders.append(folder);
|
|
}
|
|
KMMsgBase *mb = folder->getMsgBase(folderIdx);
|
|
if (!mb) //Exceptional case our .ids file is messed up
|
|
return false;
|
|
if (mb->isNew() || mb->isUnread()) {
|
|
if (mUnreadMsgs == -1) ++mUnreadMsgs;
|
|
++mUnreadMsgs;
|
|
}
|
|
}
|
|
mTotalMsgs = mSerNums.count();
|
|
fclose(mIdsStream);
|
|
mIdsStream = 0;
|
|
mUnlinked = true;
|
|
return true;
|
|
}
|
|
|
|
int KMFolderSearch::removeContents()
|
|
{
|
|
unlink(QFile::encodeName(location()));
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
mUnlinked = true;
|
|
return 0;
|
|
}
|
|
|
|
int KMFolderSearch::expungeContents()
|
|
{
|
|
setSearch(new KMSearch());
|
|
return 0;
|
|
}
|
|
|
|
int KMFolderSearch::count(bool cache) const
|
|
{
|
|
Q_UNUSED(cache);
|
|
return mSerNums.count();
|
|
}
|
|
|
|
KMMsgBase* KMFolderSearch::takeIndexEntry(int idx)
|
|
{
|
|
assert(idx >= 0 && idx < (int)mSerNums.count());
|
|
KMMsgBase *msgBase = getMsgBase(idx);
|
|
QValueVector<Q_UINT32>::iterator it = mSerNums.begin();
|
|
mSerNums.erase(&it[idx]);
|
|
return msgBase;
|
|
}
|
|
|
|
KMMsgInfo* KMFolderSearch::setIndexEntry(int idx, KMMessage *msg)
|
|
{
|
|
assert(idx >= 0 && idx < (int)mSerNums.count());
|
|
Q_UNUSED( idx );
|
|
return msg->storage()->setIndexEntry(msg->parent()->find(msg), msg);
|
|
}
|
|
|
|
void KMFolderSearch::clearIndex(bool, bool)
|
|
{
|
|
//close all referenced folders
|
|
QValueListIterator<QGuardedPtr<KMFolder> > fit;
|
|
for (fit = mFolders.begin(); fit != mFolders.end(); ++fit) {
|
|
if (!(*fit))
|
|
continue;
|
|
(*fit)->close("foldersearch");
|
|
}
|
|
mFolders.clear();
|
|
|
|
mSerNums.clear();
|
|
}
|
|
|
|
void KMFolderSearch::truncateIndex()
|
|
{
|
|
truncate(QFile::encodeName(indexLocation()), IDS_SEARCH_HEADER_LEN);
|
|
}
|
|
|
|
void KMFolderSearch::examineAddedMessage(KMFolder *aFolder, Q_UINT32 serNum)
|
|
{
|
|
if (!search() && !readSearch())
|
|
return;
|
|
if (!search()->inScope(aFolder))
|
|
return;
|
|
if (!mTempOpened) {
|
|
open("foldersearch");
|
|
mTempOpened = true;
|
|
}
|
|
|
|
if (!search()->searchPattern())
|
|
return;
|
|
|
|
int idx = -1;
|
|
KMFolder *folder = 0;
|
|
KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
|
|
assert(folder && (idx != -1));
|
|
assert(folder == aFolder);
|
|
KMFolderOpener openFolder(folder, "foldersearch");
|
|
|
|
// if we are already checking this folder, refcount
|
|
if ( mFoldersCurrentlyBeingSearched.contains( folder ) ) {
|
|
unsigned int count = mFoldersCurrentlyBeingSearched[folder];
|
|
mFoldersCurrentlyBeingSearched.replace( folder, count+1 );
|
|
} else {
|
|
connect( folder->storage(),
|
|
SIGNAL( searchDone( KMFolder*, Q_UINT32, const KMSearchPattern*, bool ) ),
|
|
this,
|
|
SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
|
|
const KMSearchPattern*, bool ) ) );
|
|
mFoldersCurrentlyBeingSearched.insert( folder, 1 );
|
|
}
|
|
folder->storage()->search( search()->searchPattern(), serNum );
|
|
}
|
|
|
|
void KMFolderSearch::slotSearchExamineMsgDone( KMFolder* folder,
|
|
Q_UINT32 serNum,
|
|
const KMSearchPattern* pattern,
|
|
bool matches )
|
|
{
|
|
if ( search()->searchPattern() != pattern ) return;
|
|
kdDebug(5006) << folder->label() << ": serNum " << serNum
|
|
<< " matches?" << matches << endl;
|
|
KMFolderOpener openFolder(folder, "foldersearch");
|
|
|
|
Q_ASSERT( mFoldersCurrentlyBeingSearched.contains( folder ) );
|
|
|
|
unsigned int count = mFoldersCurrentlyBeingSearched[folder];
|
|
if ( count == 1 ) {
|
|
disconnect( folder->storage(),
|
|
SIGNAL( searchDone( KMFolder*, Q_UINT32,
|
|
const KMSearchPattern*, bool ) ),
|
|
this,
|
|
SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
|
|
const KMSearchPattern*, bool ) ) );
|
|
mFoldersCurrentlyBeingSearched.remove( folder );
|
|
} else {
|
|
mFoldersCurrentlyBeingSearched.replace( folder, count-1 );
|
|
}
|
|
|
|
if ( !matches ) {
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
|
|
if (it != mSerNums.end()) {
|
|
removeSerNum( serNum );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if (mSearch->running()) {
|
|
// mSearch->stop();
|
|
// mExecuteSearchTimer->start( 0, true );
|
|
// } else {
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
|
|
if (it == mSerNums.end()) {
|
|
addSerNum( serNum );
|
|
}
|
|
// }
|
|
}
|
|
|
|
void KMFolderSearch::examineRemovedMessage(KMFolder *folder, Q_UINT32 serNum)
|
|
{
|
|
if (!search() && !readSearch())
|
|
return;
|
|
if (!search()->inScope(folder))
|
|
return;
|
|
if (!mTempOpened) {
|
|
open("foldersearch");
|
|
mTempOpened = true;
|
|
}
|
|
|
|
if (mSearch->running()) {
|
|
mExecuteSearchTimer->start(0, true);
|
|
} else {
|
|
removeSerNum(serNum);
|
|
}
|
|
}
|
|
|
|
void KMFolderSearch::examineChangedMessage(KMFolder *aFolder, Q_UINT32 serNum, int delta)
|
|
{
|
|
if (!search() && !readSearch())
|
|
return;
|
|
if (!search()->inScope(aFolder))
|
|
return;
|
|
if (!mTempOpened) {
|
|
open("foldersearch");
|
|
mTempOpened = true;
|
|
}
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
|
|
if (it != mSerNums.end()) {
|
|
mUnreadMsgs += delta;
|
|
emit numUnreadMsgsChanged( folder() );
|
|
emit msgChanged( folder(), serNum, delta );
|
|
}
|
|
}
|
|
|
|
void KMFolderSearch::examineInvalidatedFolder(KMFolder *folder)
|
|
{
|
|
if (!search() && !readSearch())
|
|
return;
|
|
if (!search()->inScope(folder))
|
|
return;
|
|
if (mTempOpened) {
|
|
close("foldersearch");
|
|
mTempOpened = false;
|
|
}
|
|
|
|
mInvalid = true;
|
|
if (mSearch)
|
|
mSearch->stop();
|
|
|
|
if (!mUnlinked) {
|
|
unlink(QFile::encodeName(indexLocation()));
|
|
mUnlinked = true;
|
|
}
|
|
|
|
if (!isOpened()) //give up, until the user manually opens the folder
|
|
return;
|
|
|
|
if (!mTempOpened) {
|
|
open("foldersearch");
|
|
mTempOpened = true;
|
|
}
|
|
mExecuteSearchTimer->start(0, true);
|
|
}
|
|
|
|
void KMFolderSearch::examineRemovedFolder(KMFolder *folder)
|
|
{
|
|
examineInvalidatedFolder(folder);
|
|
if (mSearch->root() == folder) {
|
|
delete mSearch;
|
|
mSearch = 0;
|
|
}
|
|
}
|
|
|
|
void KMFolderSearch::propagateHeaderChanged(KMFolder *aFolder, int idx)
|
|
{
|
|
int pos = 0;
|
|
if (!search() && !readSearch())
|
|
return;
|
|
if (!search()->inScope(aFolder))
|
|
return;
|
|
if (!mTempOpened) {
|
|
open("foldersearch");
|
|
mTempOpened = true;
|
|
}
|
|
|
|
Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum(aFolder, idx);
|
|
QValueVector<Q_UINT32>::const_iterator it;
|
|
for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
|
|
if ((*it) == serNum) {
|
|
emit msgHeaderChanged(folder(), pos);
|
|
break;
|
|
}
|
|
++pos;
|
|
}
|
|
// let's try if the message matches our search
|
|
KMFolderOpener openAFolder(aFolder, "foldersearch");
|
|
|
|
// if we are already checking this folder, refcount
|
|
if ( mFoldersCurrentlyBeingSearched.contains( aFolder ) ) {
|
|
unsigned int count = mFoldersCurrentlyBeingSearched[aFolder];
|
|
mFoldersCurrentlyBeingSearched.replace( aFolder, count+1 );
|
|
} else {
|
|
connect( aFolder->storage(),
|
|
SIGNAL( searchDone( KMFolder*, Q_UINT32, const KMSearchPattern*, bool ) ),
|
|
this,
|
|
SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
|
|
const KMSearchPattern*, bool ) ) );
|
|
mFoldersCurrentlyBeingSearched.insert( aFolder, 1 );
|
|
}
|
|
aFolder->storage()->search( search()->searchPattern(), serNum );
|
|
}
|
|
|
|
void KMFolderSearch::tryReleasingFolder(KMFolder* folder)
|
|
{
|
|
// We'll succeed releasing the folder only if mTempOpened and mOpenCount==1.
|
|
// Otherwise if mOpenCount>1 (e.g while the search dialog is up), we would just keep closing/reopening for nothing
|
|
if ( mTempOpened && mOpenCount == 1 )
|
|
{
|
|
examineInvalidatedFolder( folder );
|
|
}
|
|
}
|
|
|
|
#include "kmfoldersearch.moc"
|