kmail Library API Documentation

kmmsgindex.cpp

00001 // Author: Sam Magnuson <zachsman@wiw.org>
00002 // License GPL -- indexing logic for KMail
00003 
00004 #ifdef HAVE_CONFIG_H
00005 #include <config.h>
00006 #endif
00007 
00008 #include "kmmsgindex.h"
00009 
00010 #include "kmsearchpattern.h"
00011 #include "kmfoldersearch.h"
00012 #include "kmfoldermgr.h"
00013 #include "kmmsgdict.h"
00014 #include "kmkernel.h"
00015 
00016 #include "mimelib/message.h"
00017 #include "mimelib/headers.h"
00018 #include "mimelib/utility.h"
00019 #include "mimelib/enum.h"
00020 #include "mimelib/body.h"
00021 #include "mimelib/bodypart.h"
00022 #include "mimelib/field.h"
00023 
00024 #include <kdebug.h>
00025 
00026 #include <qdict.h>
00027 #include <qapplication.h>
00028 
00029 #include <stdio.h>
00030 #include <unistd.h>
00031 #include <fcntl.h>
00032 #include <sys/mman.h>
00033 #include <sys/types.h>
00034 #include <sys/stat.h>
00035 #include <ctype.h>
00036 #include <errno.h>
00037 
00038 //#define USE_MMAP
00039 class
00040 KMMsgIndexRef
00041 {
00042 #ifdef USE_MMAP
00043     Q_UINT32 *mRef;
00044 #endif
00045     int mFD, mSize;
00046 public:
00047     KMMsgIndexRef(int f, int size);
00048     ~KMMsgIndexRef() { }
00049     bool error();
00050 
00051     void resize(int size);
00052     void sync();
00053 
00054     bool write(int off, Q_UINT32 val);
00055     Q_UINT32 read(int off, bool *ok=NULL);
00056 };
00057 KMMsgIndexRef::KMMsgIndexRef(int f, int size) :
00058     mFD(f), mSize(size)
00059 {
00060 #ifdef USE_MMAP
00061     if(mSize != 0)
00062     mRef = (Q_UINT32*)mmap(0, mSize * sizeof(Q_INT32),
00063                    PROT_READ|PROT_WRITE, MAP_SHARED, mFD, 0);
00064     else
00065     mRef = (Q_UINT32*)MAP_FAILED;
00066 #endif
00067 }
00068 void KMMsgIndexRef::sync()
00069 {
00070 #ifdef USE_MMAP
00071     if(mRef != MAP_FAILED)
00072     msync(mRef, mSize * sizeof(Q_INT32), MS_SYNC);
00073 #endif
00074 }
00075 bool KMMsgIndexRef::error()
00076 {
00077 #ifdef USE_MMAP
00078     if(mRef == MAP_FAILED)
00079     return TRUE;
00080 #endif
00081     return FALSE;
00082 }
00083 void KMMsgIndexRef::resize(int newSize)
00084 {
00085 #ifdef USE_MMAP
00086     if(mRef != MAP_FAILED)
00087     munmap(mRef, mSize * sizeof(Q_INT32));
00088     if(ftruncate(mFD, newSize * sizeof(Q_INT32)) == -1) {
00089     for(Q_INT32 i = mSize; i < newSize; i++)
00090         ::write(mFD, &i, sizeof(i));
00091     }
00092 #endif
00093     mSize = newSize;
00094 #ifdef USE_MMAP
00095     mRef = (Q_UINT32*)mmap(0, mSize * sizeof(Q_INT32),
00096                PROT_READ|PROT_WRITE, MAP_SHARED, mFD, 0);
00097 #endif
00098 }
00099 bool KMMsgIndexRef::write(int off, Q_UINT32 val)
00100 {
00101     if(off > mSize)
00102     return FALSE;
00103 #ifdef USE_MMAP
00104     mRef[off] = val;
00105 #else
00106     lseek(mFD, off * sizeof(Q_INT32), SEEK_SET);
00107     ::write(mFD, &val, sizeof(val));
00108 #endif
00109     return TRUE;
00110 }
00111 Q_UINT32 KMMsgIndexRef::read(int off, bool *ok)
00112 {
00113     if(off > mSize) {
00114     if(ok)
00115         *ok = FALSE;
00116     return 0;
00117     }
00118 #ifdef USE_MMAP
00119     return mRef[off];
00120 #else
00121     Q_UINT32 ret;
00122     lseek(mFD, off * sizeof(Q_INT32), SEEK_SET);
00123     ::read(mFD, &ret, sizeof(ret));
00124     return ret;
00125 #endif
00126     return 0;
00127 }
00128 
00129 inline bool
00130 km_isSeparator(const char *content, uint i, uint content_len)
00131 {
00132     return !(isalnum(content[i]) ||
00133         (i < content_len - 1 && content[i+1] != '\n' &&
00134          content[i+1] != '\t' && content[i+1] != ' ' &&
00135          (content[i] == '.' || content[i] == '-' ||
00136           content[i] == '\\' || content[i] == '/' ||
00137           content[i] == '\'' || content[i] == ':')));
00138 }
00139 
00140 inline bool
00141 km_isSeparator(const QChar *content, uint i, uint content_len)
00142 {
00143     return !(content[i].isLetterOrNumber() ||
00144         (i < content_len - 1 && content[i+1] != '\n' &&
00145          content[i+1] != '\t' && content[i+1] != ' ' &&
00146          (content[i] == '.' || content[i] == '-' ||
00147           content[i] == '\\' || content[i] == '/' ||
00148           content[i] == '\'' || content[i] == ':')));
00149 }
00150 
00151 inline bool
00152 km_isSeparator(const QString &s, int i, int content_len=-1)
00153 {
00154     return km_isSeparator(s.unicode(), i,
00155               content_len < 0 ? s.length() : content_len);
00156 }
00157 
00158 inline bool
00159 km_isSeparated(const QString &f)
00160 {
00161     for(uint i=0, l=f.length(); i < f.length(); i++) {
00162     if(km_isSeparator(f.unicode(), i, l))
00163         return TRUE;
00164     }
00165     return FALSE;
00166 }
00167 
00168 inline QStringList
00169 km_separate(const QString &f)
00170 {
00171     if(!km_isSeparated(f))
00172     return QStringList(f);
00173     QStringList ret;
00174     uint i_o = 0;
00175     for(uint i=0, l=f.length(); i < f.length(); i++) {
00176     if(km_isSeparator(f.unicode(), i, l)) {
00177         QString chnk = f.mid(i_o, i - i_o).latin1();
00178         if(!chnk.isEmpty())
00179         ret << chnk;
00180         i_o = i+1;
00181     }
00182     }
00183     if(i_o != f.length()) {
00184     QString chnk = f.mid(i_o, f.length() - i_o);
00185     if(!chnk.isEmpty())
00186         ret << chnk;
00187     }
00188     return ret;
00189 }
00190 
00191 enum {
00192     HEADER_BYTEORDER = 0,
00193     HEADER_VERSION = 1,
00194     HEADER_COMPLETE = 2,
00195     HEADER_COUNT = 3,
00196     HEADER_USED = 4,
00197     HEADER_INDEXED = 5,
00198     HEADER_REMOVED = 6,
00199     HEADER_end = 7,
00200 
00201     CHUNK_HEADER_COUNT = 0,
00202     CHUNK_HEADER_USED = 1,
00203     CHUNK_HEADER_NEXT = 2,
00204     CHUNK_HEADER_end = 3,
00205 
00206     TOC_BODY = 0,
00207     TOC_HEADER_NAME = 1,
00208     TOC_HEADER_DATA = 2
00209 };
00210 #define KMMSGINDEX_VERSION 6067
00211 static int kmindex_grow_increment = 40960; //grow this many buckets at a time
00212 
00213 KMMsgIndex::KMMsgIndex(QObject *o, const char *n) :
00214     QObject(o, n), mIndexState(INDEX_IDLE), delay_cnt(0), mLastSearch()
00215 {
00216     mTermIndex.loc = kmkernel->folderMgr()->basePath() + "/.kmmsgindex_search";
00217     mTermTOC.loc = kmkernel->folderMgr()->basePath() + "/.kmmsgindex_toc";
00218     mTermProcessed.loc = kmkernel->folderMgr()->basePath() +
00219              "/.kmmsgindex_processed";
00220 }
00221 
00222 void KMMsgIndex::init()
00223 {
00224     mActiveSearches.setAutoDelete(TRUE);
00225     reset(FALSE);
00226     readIndex();
00227     connect(kmkernel->folderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
00228         this, SLOT(slotRemoveMsg(KMFolder*, Q_UINT32)));
00229     connect(kmkernel->folderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
00230         this, SLOT(slotAddMsg(KMFolder*, Q_UINT32)));
00231 }
00232 
00233 void KMMsgIndex::remove()
00234 {
00235     unlink(mTermIndex.loc.latin1());
00236     unlink(mTermTOC.loc.latin1());
00237     unlink(mTermProcessed.loc.latin1());
00238 }
00239 
00240 // resets the state of the indexer to nothing (if clean it is assumed
00241 // anything not initialized is cleaned up..
00242 void
00243 KMMsgIndex::reset(bool clean)
00244 {
00245     //active searches
00246     if(clean)
00247     mActiveSearches.clear();
00248     //create
00249     if(create.timer_id != -1) {
00250     if(clean)
00251         killTimer(create.timer_id);
00252     create.timer_id = -1;
00253     }
00254     //restore
00255     if(restore.timer_id != -1) {
00256     if(clean)
00257         killTimer(restore.timer_id);
00258     restore.timer_id = -1;
00259     }
00260     //TOC
00261     if(clean)
00262     mTermTOC.body.clear();
00263     if(mTermTOC.fd != -1) {
00264     if(clean)
00265         close(mTermTOC.fd);
00266     mTermTOC.fd = -1;
00267     }
00268     mTermTOC.h.next_hnum = 0;
00269     if(clean) {
00270     mTermTOC.h.header_lookup.clear();
00271     mTermTOC.h.headers.clear();
00272     }
00273     //processed
00274     if(mTermProcessed.fd != -1) {
00275     if(clean)
00276         close(mTermProcessed.fd);
00277     mTermProcessed.fd = -1;
00278     }
00279     if(clean)
00280     mTermProcessed.known.clear();
00281     restore.reading_processed = FALSE;
00282     //index
00283     {
00284     if(clean)
00285         delete mTermIndex.ref;
00286     mTermIndex.ref = NULL;
00287     }
00288     mTermIndex.removed = mTermIndex.indexed = 0;
00289     mTermIndex.used = mTermIndex.count = 0;
00290     if(mTermIndex.fd != -1) {
00291     if(clean)
00292         close(mTermIndex.fd);
00293     mTermIndex.fd = -1;
00294     }
00295 }
00296 
00297 // finds cnt buckets and allocates to make room
00298 int
00299 KMMsgIndex::allocTermChunk(int cnt)
00300 {
00301     int ret = mTermIndex.used;
00302     mTermIndex.used += cnt; //update the used
00303     if(mTermIndex.count < mTermIndex.used) { //time for a remap
00304     mTermIndex.count = QMAX(mTermIndex.count + kmindex_grow_increment,
00305                 mTermIndex.used);
00306     mTermIndex.ref->resize(mTermIndex.count);
00307     mTermIndex.ref->write(HEADER_COUNT, mTermIndex.count);
00308     }
00309     mTermIndex.ref->write(HEADER_USED, mTermIndex.used);
00310     return ret;
00311 }
00312 
00313 // returns whether header is a header we care about..
00314 bool
00315 KMMsgIndex::isKillHeader(const char *header, uchar header_len)
00316 {
00317     const char *watched_headers[] = { "Subject", "From", "To", "CC", "BCC",
00318                       "Reply-To", "Organization", "List-ID",
00319                       "X-Mailing-List", "X-Loop", "X-Mailer",
00320                       NULL };
00321     for(int i = 0; watched_headers[i]; i++) {
00322     if(!strncmp(header, watched_headers[i], header_len))
00323         return FALSE;
00324     }
00325     return TRUE;
00326 }
00327 
00328 //returns whether a term is in the stop list..
00329 bool
00330 KMMsgIndex::isKillTerm(const char *term, uchar term_len)
00331 {
00332     if(!term || term_len < 1)
00333     return TRUE;
00334     if(term_len <= 2) //too few letters..
00335     return TRUE;
00336     { //no numbers!
00337     int numlen = 0;
00338     if(term[numlen] == '+' || term[numlen] == '-')
00339         numlen++;
00340     for( ; numlen < term_len; numlen++) {
00341         if(!isdigit(term[numlen]) || term[numlen] == '.')
00342         break;
00343     }
00344     if(numlen == term_len - 1 && term[numlen] == '%')
00345         numlen++;
00346     if(numlen == term_len)
00347         return TRUE;
00348     }
00349     { //static kill words list
00350     static QDict<void> *killDict = NULL;
00351     if(!killDict) {
00352         killDict = new QDict<void>();
00353         const char *kills[] = { "from", "kmail", "is", "in", "and",
00354                     "it", "this", "of", "that", "on",
00355                     "you", "if", "be", "not",
00356                     "with", "for", "to", "the", "but",
00357                     NULL };
00358         for(int i = 0; kills[i]; i++)
00359         killDict->insert(kills[i], (void*)1);
00360     }
00361     if(killDict->find(term))
00362         return TRUE;
00363     }
00364     return FALSE;
00365 }
00366 
00367 // finds a free bucket starting at where to put serNum in the dbase
00368 int
00369 KMMsgIndex::addBucket(int where, Q_UINT32 serNum)
00370 {
00371     int ret = where;
00372     if(where == -1) {
00373     //enough for two (and the tail)..
00374     int first_chunk_size = CHUNK_HEADER_end + 2 + 1;
00375     int off = ret = allocTermChunk(first_chunk_size);
00376 
00377     //special case to mark the tail for the first
00378     mTermIndex.ref->write(off, off+1);
00379     off++;
00380     first_chunk_size--;
00381 
00382     //now mark in index
00383     mTermIndex.ref->write(off+CHUNK_HEADER_COUNT, first_chunk_size);
00384     mTermIndex.ref->write(off+CHUNK_HEADER_USED, CHUNK_HEADER_end + 1);
00385     mTermIndex.ref->write(off+CHUNK_HEADER_end, serNum);
00386     } else {
00387     uint len = mTermIndex.ref->read(where+CHUNK_HEADER_COUNT);
00388     if(len == mTermIndex.ref->read(where+CHUNK_HEADER_USED)) {
00389         len = 34; //let's make a bit more room this time..
00390         int blk = ret = allocTermChunk(len);
00391         mTermIndex.ref->write(where+CHUNK_HEADER_NEXT, blk);
00392         mTermIndex.ref->write(blk+CHUNK_HEADER_COUNT, len);
00393         mTermIndex.ref->write(blk+CHUNK_HEADER_USED, CHUNK_HEADER_end + 1);
00394         mTermIndex.ref->write(blk+CHUNK_HEADER_end, serNum);
00395     } else {
00396         mTermIndex.ref->write(where+
00397           mTermIndex.ref->read(where+CHUNK_HEADER_USED), serNum);
00398         mTermIndex.ref->write(where+CHUNK_HEADER_USED,
00399               mTermIndex.ref->read(where+CHUNK_HEADER_USED)+1);
00400     }
00401     }
00402     return ret;
00403 }
00404 
00405 // adds the body term to the index
00406 bool
00407 KMMsgIndex::addBodyTerm(const char *term, uchar term_len, Q_UINT32 serNum)
00408 {
00409     if(mTermIndex.ref->error())
00410     return FALSE;
00411     if(isKillTerm(term, term_len))
00412     return TRUE; //sorta..
00413     if(mIndexState == INDEX_RESTORE) //just have to finish reading..
00414     restoreState();
00415 
00416     if(!mTermTOC.body.contains(term)) {
00417     int w = addBucket(-1, serNum);
00418     mTermTOC.body.insert(term, w); //mark in TOC
00419     const uchar marker = TOC_BODY;
00420     write(mTermTOC.fd, &marker, sizeof(marker));
00421     write(mTermTOC.fd, &term_len, sizeof(term_len));
00422     write(mTermTOC.fd, term, term_len);
00423     write(mTermTOC.fd, &w, sizeof(w));
00424     } else {
00425     int map_off = mTermTOC.body[term],
00426           w = addBucket(mTermIndex.ref->read(map_off), serNum);
00427     if(w != -1)
00428         mTermIndex.ref->write(map_off, w);
00429     }
00430     return TRUE;
00431 }
00432 
00433 // adds the body term to the index
00434 bool
00435 KMMsgIndex::addHeaderTerm(Q_UINT16 hnum, const char *term,
00436               uchar term_len, Q_UINT32 serNum)
00437 {
00438     if(mTermIndex.ref->error())
00439     return FALSE;
00440     if(isKillTerm(term, term_len))
00441     return TRUE; //sorta..
00442     if(mIndexState == INDEX_RESTORE) //just have to finish reading..
00443     restoreState();
00444 
00445     if(!mTermTOC.h.headers.contains(hnum))
00446     mTermTOC.h.headers.insert(hnum, QMap<QCString, int>());
00447     if(!mTermTOC.h.headers[hnum].contains(term)) {
00448     int w = addBucket(-1, serNum);
00449     mTermTOC.h.headers[hnum].insert(term, w);
00450     const uchar marker = TOC_HEADER_DATA;
00451     write(mTermTOC.fd, &marker, sizeof(marker));
00452     write(mTermTOC.fd, &hnum, sizeof(hnum));
00453     write(mTermTOC.fd, &term_len, sizeof(term_len));
00454     write(mTermTOC.fd, term, term_len);
00455     write(mTermTOC.fd, &w, sizeof(w));
00456     } else {
00457     int map_off = mTermTOC.h.headers[hnum][term],
00458           w = addBucket(mTermIndex.ref->read(map_off), serNum);
00459     if(w != -1)
00460         mTermIndex.ref->write(map_off, w);
00461     }
00462     return TRUE;
00463 }
00464 
00465 // processes the message at serNum and returns the number of terms processed
00466 int
00467 KMMsgIndex::processMsg(Q_UINT32 serNum)
00468 {
00469     if(mIndexState == INDEX_RESTORE) {
00470     create.serNums.push(serNum);
00471     return -1;
00472     }
00473     if(mTermProcessed.known[serNum])
00474     return -1;
00475 
00476     int idx = -1;
00477     KMFolder *folder = 0;
00478     kmkernel->msgDict()->getLocation(serNum, &folder, &idx);
00479     if(!folder || (idx == -1) || (idx >= folder->count()))
00480     return -1;
00481     if(mOpenedFolders.findIndex(folder) == -1) {
00482     folder->open();
00483     mOpenedFolders.append(folder);
00484     }
00485 
00486     int ret = 0;
00487     bool unget = !folder->getMsgBase(idx)->isMessage();
00488     KMMessage *msg = folder->getMsg(idx);
00489     const DwMessage *dw_msg = msg->asDwMessage();
00490     DwHeaders& headers = dw_msg->Headers();
00491     uchar build_i = 0;
00492     char build_str[255];
00493 
00494     //process header
00495     for(DwField *field = headers.FirstField(); field; field = field->Next()) {
00496     if(isKillHeader(field->FieldNameStr().data(),
00497             field->FieldNameStr().length()))
00498         continue;
00499     const char *name    = field->FieldNameStr().c_str(),
00500            *content = field->FieldBodyStr().data();
00501     uint content_len = field->FieldBodyStr().length();
00502     Q_UINT16 hnum = 0;
00503     if(mTermTOC.h.header_lookup.contains(name)) {
00504         hnum = mTermTOC.h.header_lookup[name];
00505     } else {
00506         hnum = mTermTOC.h.next_hnum++;
00507         mTermTOC.h.header_lookup.insert(name, hnum);
00508         const uchar marker = TOC_HEADER_NAME;
00509         write(mTermTOC.fd, &marker, sizeof(marker));
00510         uchar len = field->FieldNameStr().length();
00511         write(mTermTOC.fd, &len, sizeof(len));
00512         write(mTermTOC.fd, name, len);
00513         write(mTermTOC.fd, &hnum, sizeof(hnum));
00514     }
00515     for(uint i = 0; i < content_len; i++) {
00516         if(build_i < 254 && !km_isSeparator(content, i, content_len)) {
00517         build_str[build_i++] = tolower(content[i]);
00518         } else if(build_i) {
00519         build_str[build_i] = 0;
00520         if(addHeaderTerm(hnum, build_str, build_i, serNum))
00521             ret++;
00522         build_i = 0;
00523         }
00524     }
00525     if(build_i) {
00526         build_str[build_i] = 0;
00527         if(addHeaderTerm(hnum, build_str, build_i, serNum))
00528         ret++;
00529         build_i = 0;
00530     }
00531     }
00532 
00533     //process body
00534     const DwEntity *dw_ent = msg->asDwMessage();
00535     DwString dw_body;
00536     DwString body;
00537     if(dw_ent && dw_ent->hasHeaders() && dw_ent->Headers().HasContentType() &&
00538        (dw_ent->Headers().ContentType().Type() == DwMime::kTypeText)) {
00539     dw_body = dw_ent->Body().AsString();
00540     } else {
00541     dw_ent = msg->getFirstDwBodyPart();
00542     if (dw_ent)
00543         dw_body = msg->getFirstDwBodyPart()->AsString();
00544     }
00545     if(dw_ent && dw_ent->hasHeaders() && dw_ent->Headers().HasContentType() &&
00546        (dw_ent->Headers().ContentType().Type() == DwMime::kTypeText)) {
00547       DwHeaders& headers = dw_ent->Headers();
00548       if(headers.HasContentTransferEncoding()) {
00549     switch(headers.ContentTransferEncoding().AsEnum()) {
00550     case DwMime::kCteBase64: {
00551         DwString raw_body = dw_body;
00552         DwDecodeBase64(raw_body, body);
00553         break; }
00554     case DwMime::kCteQuotedPrintable: {
00555         DwString raw_body = dw_body;
00556         DwDecodeQuotedPrintable(raw_body, body);
00557         break; }
00558     default:
00559         body = dw_body;
00560         break;
00561     }
00562       } else {
00563       body = dw_body;
00564       }
00565     }
00566     QDict<void> found_terms;
00567     const char *body_s = body.data();
00568     for(uint i = 0; i < body.length(); i++) {
00569     if(build_i < 254 && !km_isSeparator(body_s, i, body.length())) {
00570         build_str[build_i++] = tolower(body_s[i]);
00571     } else if(build_i) {
00572         build_str[build_i] = 0;
00573         if(!found_terms[build_str] && addBodyTerm(build_str, build_i,
00574                               serNum)) {
00575         found_terms.insert(build_str, (void*)1);
00576         ret++;
00577         }
00578         build_i = 0;
00579     }
00580     }
00581     if(build_i) {
00582     build_str[build_i] = 0;
00583     if(!found_terms[build_str] && addBodyTerm(build_str,
00584                           build_i, serNum)) {
00585         found_terms.insert(build_str, (void*)1);
00586         ret++;
00587     }
00588     }
00589     if (unget)
00590       folder->unGetMsg(idx); //I don't need it anymore..
00591     mTermIndex.ref->write(HEADER_INDEXED, ++mTermIndex.indexed);
00592     mTermProcessed.known.insert(serNum, (void*)1);
00593     write(mTermProcessed.fd, &serNum, sizeof(serNum));
00594     return ret;
00595 }
00596 
00597 //Determines if it is time for another cleanup
00598 bool
00599 KMMsgIndex::isTimeForClean()
00600 {
00601     return (mTermIndex.removed > 2500 && //minimum
00602         mTermIndex.removed * 4 >= mTermIndex.indexed && //fraction removed
00603         (mLastSearch.isNull() ||  //never
00604          mLastSearch.secsTo(QTime::currentTime()) > 60 * 60 * 2)); //hours
00605 }
00606 
00607 //removes bogus entries from the index, and optimizes the index file
00608 void
00609 KMMsgIndex::cleanUp()
00610 {
00611     if(mIndexState != INDEX_IDLE)
00612     return;
00613     reset(TRUE);
00614     remove();
00615     recreateIndex();
00616 }
00617 
00618 // flushes all open file descriptors..
00619 void
00620 KMMsgIndex::flush()
00621 {
00622 #if 0
00623     mTermIndex.ref->sync();
00624     sync();
00625 #endif
00626 }
00627 
00628 // slot fired when a serial number is no longer used
00629 void
00630 KMMsgIndex::slotRemoveMsg(KMFolder *, Q_UINT32)
00631 {
00632     mTermIndex.ref->write(HEADER_REMOVED, ++mTermIndex.removed);
00633 }
00634 
00635 // slot fired when new serial numbers are allocated..
00636 void
00637 KMMsgIndex::slotAddMsg(KMFolder *, Q_UINT32 serNum)
00638 {
00639     if(mIndexState == INDEX_CREATE) {
00640     create.serNums.push(serNum);
00641     } else if(isTimeForClean()) {
00642     cleanUp();
00643     } else {
00644     processMsg(serNum);
00645     flush();
00646     }
00647 }
00648 
00649 // handles the lazy processing of messages
00650 void
00651 KMMsgIndex::timerEvent(QTimerEvent *e)
00652 {
00653     if(qApp->hasPendingEvents()) //nah, some other time..
00654     delay_cnt = 10;
00655     else if(delay_cnt)
00656     --delay_cnt;
00657     else if(mIndexState == INDEX_CREATE && e->timerId() == create.timer_id)
00658     createState(FALSE);
00659     else if(mIndexState == INDEX_RESTORE && e->timerId() == restore.timer_id)
00660     restoreState(FALSE);
00661 }
00662 
00663 bool
00664 KMMsgIndex::createState(bool finish)
00665 {
00666     int terms = 0, processed = 0, skipped = 0;
00667     const int max_terms = 300, max_process = 30;
00668     while(!create.serNums.isEmpty()) {
00669     if(!finish &&
00670        (terms >= max_terms || processed >= max_process ||
00671         skipped >= (max_process*4))) {
00672         flush();
00673         return TRUE;
00674     }
00675     int cnt = processMsg(create.serNums.pop());
00676     if(cnt == -1) {
00677         skipped++;
00678     } else {
00679         terms += cnt;
00680         processed++;
00681     }
00682     }
00683     if(KMFolder *f = create.folders.pop()) {
00684     if(mOpenedFolders.findIndex(f) == -1) {
00685         f->open();
00686         mOpenedFolders.append(f);
00687     }
00688     for(int i = 0, s; i < f->count(); ++i) {
00689         s = kmkernel->msgDict()->getMsgSerNum(f, i);
00690         if(finish ||
00691            (terms < max_terms && processed < max_process &&
00692         skipped < (max_process*4))) {
00693         int cnt = processMsg(s);
00694         if(cnt == -1) {
00695             skipped++;
00696         } else {
00697             terms += cnt;
00698             processed++;
00699         }
00700         } else if(!mTermProcessed.known[s]){
00701         create.serNums.push(s);
00702         }
00703     }
00704     if(finish) {
00705         while(!createState(TRUE));
00706         return TRUE;
00707     }
00708     } else {
00709     mIndexState = INDEX_IDLE;
00710     killTimer(create.timer_id);
00711     create.timer_id = -1;
00712     QValueListConstIterator<QGuardedPtr<KMFolder> > it;
00713     for (it = mOpenedFolders.begin();
00714          it != mOpenedFolders.end(); ++it) {
00715         KMFolder *folder = *it;
00716         if(folder)
00717         folder->close();
00718     }
00719     mOpenedFolders.clear();
00720     create.folders.clear();
00721     mTermIndex.ref->write(HEADER_COMPLETE, 1);
00722     return TRUE;
00723     }
00724     flush();
00725     return FALSE;
00726 }
00727 
00728 // reads in some terms from the index (non-blocking) if finish is true it
00729 // will read in everything left to do. It is possible for this to turn from the
00730 // RESTORE state into the CREATE state - so you must handle this case if you
00731 // need to use the index immediately (ie finish is true)
00732 bool
00733 KMMsgIndex::restoreState(bool finish) {
00734     if(mIndexState != INDEX_RESTORE)
00735     return FALSE;
00736     uchar marker, len;
00737     char in[255];
00738     Q_UINT32 off;
00739     for(int cnt = 0; finish || cnt < 400; cnt++) {
00740     if(restore.reading_processed) {
00741         Q_UINT32 ser;
00742         if(!read(mTermProcessed.fd, &ser, sizeof(ser))) {
00743         mIndexState = INDEX_IDLE;
00744         killTimer(restore.timer_id);
00745         restore.timer_id = -1;
00746         if(restore.restart_index) {
00747             mIndexState = INDEX_CREATE;
00748             syncIndex();
00749         }
00750         break;
00751         }
00752         mTermProcessed.known.insert(ser, (void*)1);
00753     } else {
00754         if(!read(mTermTOC.fd, &marker, sizeof(marker)))
00755         restore.reading_processed = TRUE;
00756         if(marker == TOC_BODY) {
00757         read(mTermTOC.fd, &len, sizeof(len));
00758         read(mTermTOC.fd, in, len);
00759         in[len] = 0;
00760         read(mTermTOC.fd, &off, sizeof(off));
00761         mTermTOC.body.insert(in, off);
00762         } else if(marker == TOC_HEADER_DATA) {
00763         Q_UINT16 hnum;
00764         read(mTermTOC.fd, &hnum, sizeof(hnum));
00765         read(mTermTOC.fd, &len, sizeof(len));
00766         read(mTermTOC.fd, in, len);
00767         in[len] = 0;
00768         read(mTermTOC.fd, &off, sizeof(off));
00769         if(!mTermTOC.h.headers.contains(hnum)) {
00770             QMap<QCString, int> map;
00771             map.insert(in, off);
00772             mTermTOC.h.headers.insert(hnum, map);
00773         } else {
00774             mTermTOC.h.headers[hnum].insert(in, off);
00775         }
00776         } else if(marker == TOC_HEADER_NAME) {
00777         read(mTermTOC.fd, &len, sizeof(len));
00778         read(mTermTOC.fd, in, len);
00779         in[len] = 0;
00780         Q_UINT16 hnum;
00781         read(mTermTOC.fd, &hnum, sizeof(hnum));
00782         if(!mTermTOC.h.header_lookup.contains(in)) {
00783             mTermTOC.h.header_lookup.insert(in, hnum);
00784             mTermTOC.h.next_hnum = hnum + 1;
00785         }
00786         }
00787     }
00788     }
00789     return TRUE;
00790 }
00791 
00792 // nulls the current index and begins a refresh of the indexed data..
00793 bool
00794 KMMsgIndex::recreateIndex()
00795 {
00796     if(mIndexState != INDEX_IDLE)
00797     return FALSE;
00798 
00799     mIndexState = INDEX_CREATE;
00800     mTermProcessed.fd = open(mTermProcessed.loc.latin1(),
00801                  O_WRONLY|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
00802     mTermTOC.fd = open(mTermTOC.loc.latin1(),
00803                O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
00804     mTermIndex.fd = open(mTermIndex.loc.latin1(),
00805              O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
00806     mTermIndex.count = kmindex_grow_increment;
00807     mTermIndex.used = HEADER_end;
00808     mTermIndex.ref = new KMMsgIndexRef(mTermIndex.fd, 0);
00809     mTermIndex.ref->resize(mTermIndex.count);
00810     mTermIndex.ref->write(HEADER_BYTEORDER, 0x12345678);
00811     mTermIndex.ref->write(HEADER_VERSION, KMMSGINDEX_VERSION);
00812     mTermIndex.ref->write(HEADER_COMPLETE, 0); //marker for incomplete index
00813     mTermIndex.ref->write(HEADER_COUNT, mTermIndex.count);
00814     mTermIndex.ref->write(HEADER_USED, mTermIndex.used);//including this header
00815     mTermIndex.ref->write(HEADER_INDEXED, mTermIndex.indexed);
00816     mTermIndex.ref->write(HEADER_REMOVED, mTermIndex.removed);
00817     syncIndex();
00818     return TRUE;
00819 }
00820 
00821 // processes all current messages as if they were newly added
00822 void
00823 KMMsgIndex::syncIndex()
00824 {
00825     if(mIndexState != INDEX_CREATE)
00826     return;
00827     QValueStack<QGuardedPtr<KMFolderDir> > folders;
00828     folders.push(&(kmkernel->folderMgr()->dir()));
00829     while(KMFolderDir *dir = folders.pop()) {
00830     for(KMFolderNode *child = dir->first(); child; child = dir->next()) {
00831         if(child->isDir())
00832         folders.push((KMFolderDir*)child);
00833         else
00834         create.folders.push((KMFolder*)child);
00835     }
00836     }
00837     if(create.timer_id == -1)
00838     create.timer_id = startTimer(0);
00839 }
00840 
00841 // read the existing index and load into memory
00842 void
00843 KMMsgIndex::readIndex()
00844 {
00845     if(mIndexState != INDEX_IDLE)
00846     return;
00847     mIndexState = INDEX_RESTORE;
00848     bool read_success = FALSE;
00849     if((mTermTOC.fd = open(mTermTOC.loc.latin1(), O_RDWR)) != -1) {
00850     if((mTermIndex.fd = open(mTermIndex.loc.latin1(), O_RDWR)) != -1) {
00851         mTermProcessed.fd = open(mTermProcessed.loc.latin1(), O_RDWR);
00852 
00853         Q_INT32 byteOrder = 0, version;
00854         read(mTermIndex.fd, &byteOrder, sizeof(byteOrder));
00855         if(byteOrder != 0x12345678)
00856         goto error_with_read;
00857         read(mTermIndex.fd, &version, sizeof(version));
00858         if(version != KMMSGINDEX_VERSION)
00859         goto error_with_read;
00860         Q_UINT32 complete_index = 0;
00861         read(mTermIndex.fd, &complete_index, sizeof(complete_index));
00862         restore.restart_index = !complete_index;
00863         read(mTermIndex.fd, &mTermIndex.count, sizeof(mTermIndex.count));
00864         read(mTermIndex.fd, &mTermIndex.used, sizeof(mTermIndex.used));
00865         read(mTermIndex.fd, &mTermIndex.indexed,
00866          sizeof(mTermIndex.indexed));
00867         read(mTermIndex.fd, &mTermIndex.removed,
00868          sizeof(mTermIndex.removed));
00869         mTermIndex.ref = new KMMsgIndexRef(mTermIndex.fd,
00870                            mTermIndex.count);
00871         if(mTermIndex.ref->error())
00872         goto error_with_read;
00873         restore.timer_id = startTimer(0);
00874         read_success = TRUE;
00875     }
00876     }
00877 error_with_read:
00878     if(!read_success) {
00879     mIndexState = INDEX_IDLE;
00880     reset();
00881     remove();
00882     recreateIndex();
00883     }
00884 }
00885 
00886 // returns whether rule is a valid rule to be processed by the indexer
00887 bool
00888 KMMsgIndex::canHandleQuery(KMSearchRule *rule)
00889 {
00890     if(mIndexState == INDEX_RESTORE) //just have to finish reading..
00891     restoreState(); //this might flip us into INDEX_CREATE state..
00892     if(mIndexState != INDEX_IDLE) //not while we are doing other stuff..
00893     return FALSE;
00894     if(rule->field().isEmpty() || rule->contents().isEmpty()) //not a real search
00895     return TRUE;
00896     if(rule->function() != KMSearchRule::FuncEquals &&
00897        rule->function() != KMSearchRule::FuncContains) {
00898     return FALSE;
00899     } else if(rule->field().left(1) == "<") {
00900     if(rule->field() == "<body>" || rule->field() == "<message>") {
00901         if(rule->function() != KMSearchRule::FuncContains)
00902         return FALSE;
00903     } else if(rule->field() != "<recipients>") { //unknown..
00904         return FALSE;
00905     }
00906     } else if(isKillHeader(rule->field().data(), rule->field().length())) {
00907     return FALSE;
00908     }
00909     QString match = rule->contents().lower();     //general case
00910     if(km_isSeparated(match)) {
00911     uint killed = 0;
00912     QStringList sl = km_separate(match);
00913     for(QStringList::Iterator it = sl.begin();
00914         it != sl.end(); ++it) {
00915         QString str = (*it).lower();
00916         if(isKillTerm(str.latin1(), str.length()))
00917         killed++;
00918     }
00919     if(killed == sl.count())
00920         return FALSE;
00921     } else if(isKillTerm(match.latin1(), match.length())) {
00922     return FALSE;
00923     }
00924     return TRUE;
00925 }
00926 
00927 // returns whether pat is a valid pattern to be processed by the indexer
00928 bool
00929 KMMsgIndex::canHandleQuery(KMSearchPattern *pat)
00930 {
00931     if(mIndexState == INDEX_RESTORE) //just have to finish reading..
00932     restoreState(); //this might flip us into INDEX_CREATE state..
00933     if(mIndexState != INDEX_IDLE) //not while we are creating the index..
00934     return FALSE;
00935     if(pat->op() != KMSearchPattern::OpAnd &&
00936        pat->op() != KMSearchPattern::OpOr)
00937     return FALSE;
00938     for(QPtrListIterator<KMSearchRule> it(*pat); it.current(); ++it) {
00939     if(!canHandleQuery((*it)))
00940         return FALSE;
00941     }
00942     return TRUE;
00943 }
00944 
00945 // returns the data set at begin_chunk through to end_chunk
00946 void
00947 KMMsgIndex::values(int begin_chunk, int end_chunk, QValueList<Q_UINT32> &lst)
00948 {
00949     lst.clear();
00950     for(int off = begin_chunk; TRUE;
00951     off = mTermIndex.ref->read(off+CHUNK_HEADER_NEXT)) {
00952     uint used = mTermIndex.ref->read(off+CHUNK_HEADER_USED);
00953     for(uint i = CHUNK_HEADER_end; i < used; i++)
00954         lst << mTermIndex.ref->read(off+i);
00955     if(mTermIndex.ref->read(off) != used || off == end_chunk)
00956         break;
00957     }
00958 }
00959 
00960 // returns the data set at begin_chunk through to end_chunk
00961 void
00962 KMMsgIndex::values(int begin_chunk, int end_chunk, QIntDict<void> &dct)
00963 {
00964     dct.clear();
00965     for(int off = begin_chunk; TRUE;
00966     off = mTermIndex.ref->read(off+CHUNK_HEADER_NEXT)) {
00967     uint used = mTermIndex.ref->read(off+CHUNK_HEADER_USED);
00968     for(uint i = CHUNK_HEADER_end; i < used; i++)
00969         dct.insert(mTermIndex.ref->read(off+i), (void*)1);
00970     if(mTermIndex.ref->read(off) != used || off == end_chunk)
00971         break;
00972     }
00973 }
00974 
00975 // performs an actual search in the index
00976 QValueList<Q_UINT32>
00977 KMMsgIndex::find(QString data, bool contains, KMSearchRule *rule,
00978          bool body, Q_UINT16 hnum)
00979 {
00980     QValueList<Q_UINT32> ret;
00981     if(!body && !mTermTOC.h.headers.contains(hnum))
00982     return ret;
00983     if(contains) {
00984     QIntDict<void> foundDict;
00985     QMap<QCString, int> *map = &(mTermTOC.body);
00986     if(!body)
00987         map = &(mTermTOC.h.headers[hnum]);
00988     QStringList sl = km_separate(data);
00989     for(QStringList::Iterator slit = sl.begin();
00990         slit != sl.end(); ++slit) {
00991         for(QMapIterator<QCString, int> it = map->begin();
00992         it != map->end(); ++it) {
00993         QString qstr = it.key();
00994         bool matches = FALSE;
00995         if(sl.count() == 1)
00996             matches = qstr.contains((*slit));
00997         else if(slit == sl.begin())
00998             matches = qstr.endsWith((*slit));
00999         else if(slit == sl.end())
01000             matches = qstr.startsWith((*slit));
01001         else
01002             matches = (qstr == (*slit));
01003         if(matches) {
01004             QValueList<Q_UINT32> tmp = find(it.key(), FALSE, rule,
01005                             body, hnum);
01006             for(QValueListIterator<Q_UINT32> tmp_it = tmp.begin();
01007             tmp_it != tmp.end(); ++tmp_it) {
01008             if(!foundDict[(*tmp_it)])
01009                 foundDict.insert((*tmp_it), (void*)1);
01010             }
01011         }
01012         }
01013     }
01014     for(QIntDictIterator<void> it(foundDict); it.current(); ++it)
01015         ret << it.currentKey();
01016     return ret;
01017     }
01018     mLastSearch = QTime::currentTime();
01019 
01020     bool exhaustive_search = FALSE;
01021     if(km_isSeparated(data)) { //phrase search..
01022     bool first = TRUE;
01023     QIntDict<void> foundDict;
01024     QStringList sl = km_separate(data);
01025     for(QStringList::Iterator it = sl.begin(); it != sl.end(); ++it) {
01026         if(!isKillTerm((*it).latin1(), (*it).length())) {
01027         QCString cstr((*it).latin1());
01028         int map_off = 0;
01029         if(body) {
01030             if(!mTermTOC.body.contains(cstr))
01031             return ret;
01032             map_off = mTermTOC.body[cstr];
01033         } else {
01034             if(!mTermTOC.h.headers[hnum].contains(cstr))
01035             return ret;
01036             map_off = mTermTOC.h.headers[hnum][cstr];
01037         }
01038         if(first) {
01039             first = FALSE;
01040             values(map_off+1, mTermIndex.ref->read(map_off),
01041                foundDict);
01042         } else {
01043             QIntDict<void> andDict;
01044             QValueList<Q_UINT32> tmp;
01045             values(map_off+1, mTermIndex.ref->read(map_off), tmp);
01046             for(QValueListIterator<Q_UINT32> it = tmp.begin();
01047             it != tmp.end(); ++it) {
01048             if(foundDict[(*it)])
01049                 andDict.insert((*it), (void*)1);
01050             }
01051             foundDict = andDict;
01052         }
01053         }
01054     }
01055     for(QIntDictIterator<void> it(foundDict); it.current(); ++it)
01056         ret << it.currentKey();
01057     exhaustive_search = TRUE;
01058     } else if(!isKillTerm(data.latin1(), data.length())) {
01059     QCString cstr(data.latin1());
01060     int map_off = -1;
01061     if(body) {
01062         if(mTermTOC.body.contains(cstr))
01063         map_off = mTermTOC.body[cstr];
01064     } else {
01065         if(mTermTOC.h.headers[hnum].contains(cstr))
01066         map_off = mTermTOC.h.headers[hnum][cstr];
01067     }
01068     if(map_off != -1)
01069         values(map_off+1, mTermIndex.ref->read(map_off), ret);
01070     }
01071     if(!ret.isEmpty() && rule &&
01072        (exhaustive_search || rule->function() == KMSearchRule::FuncEquals)) {
01073     QValueList<Q_UINT32> tmp;
01074     for(QValueListIterator<Q_UINT32> it = ret.begin();
01075         it != ret.end(); ++it) {
01076         int idx = -1, ser = (*it);
01077         KMFolder *folder = 0;
01078         kmkernel->msgDict()->getLocation(ser, &folder, &idx);
01079         if(!folder || (idx == -1))
01080               continue;
01081         KMMessage *msg = folder->getMsg(idx);
01082         if(rule->matches(msg))
01083         tmp << ser;
01084     }
01085     return tmp;
01086     }
01087     return ret;
01088 }
01089 
01090 
01091 // processes rule and performs the indexed look up, if exhaustive_search
01092 // is true it will interpret body() as a full phrase rather than AND'd set
01093 QValueList<Q_UINT32>
01094 KMMsgIndex::query(KMSearchRule *rule, bool exhaustive_search)
01095 {
01096     if(!canHandleQuery(rule) || rule->field().isEmpty() || rule->contents().isEmpty())
01097     return QValueList<Q_UINT32>();
01098     if(rule->field().left(1) == "<") {
01099     if((rule->field() == "<body>" || rule->field() == "<message>") &&
01100        rule->function() == KMSearchRule::FuncContains) {
01101         return find(rule->contents().lower(),
01102             TRUE, exhaustive_search ? rule : NULL, TRUE);
01103     } else if(rule->field() == "<recipients>") {
01104         bool first = TRUE;
01105         QIntDict<void> foundDict;
01106         QString contents = rule->contents().lower();
01107         const char *hdrs[] = { "To", "CC", "BCC", NULL };
01108         for(int i = 0; hdrs[i]; i++) {
01109         int l = strlen(hdrs[i]);
01110         if(isKillHeader(hdrs[i], l)) //can't really happen
01111             continue;
01112         QValueList<Q_UINT32> tmp = find(contents,
01113             rule->function() == KMSearchRule::FuncContains,
01114             exhaustive_search ? rule : NULL, FALSE,
01115             mTermTOC.h.header_lookup[hdrs[i]]);
01116         if(first) {
01117             first = FALSE;
01118             for(QValueListIterator<Q_UINT32> it = tmp.begin();
01119             it != tmp.end(); ++it)
01120             foundDict.insert((*it), (void*)1);
01121         } else {
01122             for(QValueListIterator<Q_UINT32> it = tmp.begin();
01123             it != tmp.end(); ++it) {
01124             if(!foundDict[(*it)])
01125                 foundDict.insert((*it), (void*)1);
01126             }
01127         }
01128         }
01129         QValueList<Q_UINT32> ret;
01130         for(QIntDictIterator<void> it(foundDict); it.current(); ++it)
01131         ret << it.currentKey();
01132         return ret;
01133     }
01134     return QValueList<Q_UINT32>(); //can't really happen..
01135     }
01136     //general header case..
01137     if(!mTermTOC.h.header_lookup.contains(rule->field()))
01138     return QValueList<Q_UINT32>();
01139     return find(rule->contents().lower(),
01140         rule->function() == KMSearchRule::FuncContains,
01141         exhaustive_search ? rule : NULL, FALSE,
01142         mTermTOC.h.header_lookup[rule->field()]);
01143 
01144 }
01145 
01146 // processes rule and performs the indexed look up, if exhaustive_search
01147 // is true it will interpret body()[s] as full phrases rather than AND'd sets
01148 QValueList<Q_UINT32>
01149 KMMsgIndex::query(KMSearchPattern *pat, bool exhaustive_search)
01150 {
01151     QValueList<Q_UINT32> ret;
01152     if(pat->isEmpty() || !canHandleQuery(pat))
01153     return ret;
01154 
01155     if(pat->count() == 1) {
01156     ret = query(pat->first(), exhaustive_search);
01157     } else {
01158     bool first = TRUE;
01159     QIntDict<void> foundDict;
01160     for(QPtrListIterator<KMSearchRule> it(*pat); it.current(); ++it) {
01161         if((*it)->field().isEmpty() || (*it)->contents().isEmpty())
01162         continue;
01163         QValueList<Q_UINT32> tmp = query((*it), exhaustive_search);
01164         if(first) {
01165         first = FALSE;
01166         for(QValueListIterator<Q_UINT32> it = tmp.begin();
01167             it != tmp.end(); ++it)
01168             foundDict.insert((long int)(*it), (void*)1);
01169         } else {
01170         if(pat->op() == KMSearchPattern::OpAnd) {
01171             QIntDict<void> andDict;
01172             for(QValueListIterator<Q_UINT32> it = tmp.begin();
01173             it != tmp.end(); ++it) {
01174             if(foundDict[(*it)])
01175                 andDict.insert((*it), (void*)1);
01176             }
01177             foundDict = andDict;
01178         } else if(pat->op() == KMSearchPattern::OpOr) {
01179             for(QValueListIterator<Q_UINT32> it = tmp.begin();
01180             it != tmp.end(); ++it) {
01181             if(!foundDict[(*it)])
01182                 foundDict.insert((long int)(*it), (void*)1);
01183             }
01184         }
01185         }
01186     }
01187     for(QIntDictIterator<void> it(foundDict); it.current(); ++it)
01188         ret << it.currentKey();
01189     }
01190     return ret;
01191 }
01192 
01193 // Code to bind to a KMSearch
01194 KMIndexSearchTarget::KMIndexSearchTarget(KMSearch *s) : QObject(NULL, NULL),
01195                             mVerifyResult(FALSE)
01196 {
01197     mSearch = s;
01198     mId = startTimer(0);
01199     {
01200     QValueList<Q_UINT32> lst = kmkernel->msgIndex()->query(
01201         s->searchPattern(), FALSE);
01202     for(QValueListConstIterator<Q_UINT32> it = lst.begin();
01203         it != lst.end(); ++it)
01204         mSearchResult.push((*it));
01205     }
01206     for(QPtrListIterator<KMSearchRule> it(*s->searchPattern());
01207     it.current(); ++it) {
01208     if((*it)->function() != KMSearchRule::FuncContains ||
01209        km_isSeparated((*it)->contents())) {
01210         mVerifyResult = TRUE;
01211         break;
01212     }
01213     }
01214     QObject::connect(this, SIGNAL(proxyFound(Q_UINT32)),
01215              s, SIGNAL(found(Q_UINT32)));
01216     QObject::connect(this, SIGNAL(proxyFinished(bool)),
01217              s, SIGNAL(finished(bool)));
01218 }
01219 KMIndexSearchTarget::~KMIndexSearchTarget()
01220 {
01221     stop();
01222     QValueListConstIterator<QGuardedPtr<KMFolder> > it;
01223     for (it = mOpenedFolders.begin(); it != mOpenedFolders.end(); ++it) {
01224     KMFolder *folder = *it;
01225     if(folder)
01226         folder->close();
01227     }
01228     mOpenedFolders.clear();
01229 }
01230 void
01231 KMIndexSearchTarget::timerEvent(QTimerEvent *)
01232 {
01233     if(qApp->hasPendingEvents())
01234     return; //no time now
01235     bool finished = FALSE;
01236     if(mSearch) {
01237     KMFolder *folder;
01238     const uint max_src = mVerifyResult ? 100 : 500;
01239     int stop_at = QMIN(mSearchResult.count(), max_src);
01240     for(int i = 0, idx; i < stop_at; i++) {
01241         Q_UINT32 serNum = mSearchResult.pop();
01242         kmkernel->msgDict()->getLocation(serNum, &folder, &idx);
01243         if (!folder || (idx == -1))
01244         continue;
01245         if(mSearch->inScope(folder)) {
01246         mSearch->setSearchedCount(mSearch->searchedCount()+1);
01247         mSearch->setCurrentFolder(folder->label());
01248         if(mVerifyResult) { //full phrase..
01249             if(mOpenedFolders.findIndex(folder) == -1) {
01250             folder->open();
01251             mOpenedFolders.append(folder);
01252             }
01253             if(!mSearch->searchPattern()->matches(
01254                folder->getDwString(idx)))
01255             continue;
01256         }
01257         mSearch->setFoundCount(mSearch->foundCount()+1);
01258         emit proxyFound(serNum);
01259         }
01260     }
01261     if(mSearchResult.isEmpty())
01262         finished = TRUE;
01263     } else {
01264     finished = TRUE;
01265     }
01266     if(finished) {
01267     if(mSearch && mSearch->running())
01268         mSearch->setRunning(FALSE);
01269     stop(TRUE);
01270     killTimer(mId);
01271     kmkernel->msgIndex()->stopQuery(id());
01272     }
01274 }
01275 bool
01276 KMMsgIndex::startQuery(KMSearch *s)
01277 {
01278     if(!canHandleQuery(s->searchPattern()))
01279     return FALSE;
01280     KMIndexSearchTarget *targ = new KMIndexSearchTarget(s);
01281     mActiveSearches.insert(targ->id(), targ);
01282     return TRUE;
01283 }
01284 bool
01285 KMMsgIndex::stopQuery(KMSearch *s)
01286 {
01287     int id = -1;
01288     for(QIntDictIterator<KMIndexSearchTarget> it(mActiveSearches);
01289     it.current(); ++it) {
01290     if(it.current()->search() == s) {
01291         it.current()->stop(FALSE);
01292         id = it.currentKey();
01293         break;
01294     }
01295     }
01296     if(id == -1)
01297     return FALSE;
01298     return stopQuery(id);
01299 }
01300 #include "kmmsgindex.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:37:33 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003