00001
00002
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
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;
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
00241
00242 void
00243 KMMsgIndex::reset(bool clean)
00244 {
00245
00246 if(clean)
00247 mActiveSearches.clear();
00248
00249 if(create.timer_id != -1) {
00250 if(clean)
00251 killTimer(create.timer_id);
00252 create.timer_id = -1;
00253 }
00254
00255 if(restore.timer_id != -1) {
00256 if(clean)
00257 killTimer(restore.timer_id);
00258 restore.timer_id = -1;
00259 }
00260
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
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
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
00298 int
00299 KMMsgIndex::allocTermChunk(int cnt)
00300 {
00301 int ret = mTermIndex.used;
00302 mTermIndex.used += cnt;
00303 if(mTermIndex.count < mTermIndex.used) {
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
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
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)
00335 return TRUE;
00336 {
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 {
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
00368 int
00369 KMMsgIndex::addBucket(int where, Q_UINT32 serNum)
00370 {
00371 int ret = where;
00372 if(where == -1) {
00373
00374 int first_chunk_size = CHUNK_HEADER_end + 2 + 1;
00375 int off = ret = allocTermChunk(first_chunk_size);
00376
00377
00378 mTermIndex.ref->write(off, off+1);
00379 off++;
00380 first_chunk_size--;
00381
00382
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;
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
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;
00413 if(mIndexState == INDEX_RESTORE)
00414 restoreState();
00415
00416 if(!mTermTOC.body.contains(term)) {
00417 int w = addBucket(-1, serNum);
00418 mTermTOC.body.insert(term, w);
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
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;
00442 if(mIndexState == INDEX_RESTORE)
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
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
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
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);
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
00598 bool
00599 KMMsgIndex::isTimeForClean()
00600 {
00601 return (mTermIndex.removed > 2500 &&
00602 mTermIndex.removed * 4 >= mTermIndex.indexed &&
00603 (mLastSearch.isNull() ||
00604 mLastSearch.secsTo(QTime::currentTime()) > 60 * 60 * 2));
00605 }
00606
00607
00608 void
00609 KMMsgIndex::cleanUp()
00610 {
00611 if(mIndexState != INDEX_IDLE)
00612 return;
00613 reset(TRUE);
00614 remove();
00615 recreateIndex();
00616 }
00617
00618
00619 void
00620 KMMsgIndex::flush()
00621 {
00622 #if 0
00623 mTermIndex.ref->sync();
00624 sync();
00625 #endif
00626 }
00627
00628
00629 void
00630 KMMsgIndex::slotRemoveMsg(KMFolder *, Q_UINT32)
00631 {
00632 mTermIndex.ref->write(HEADER_REMOVED, ++mTermIndex.removed);
00633 }
00634
00635
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
00650 void
00651 KMMsgIndex::timerEvent(QTimerEvent *e)
00652 {
00653 if(qApp->hasPendingEvents())
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
00729
00730
00731
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
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);
00813 mTermIndex.ref->write(HEADER_COUNT, mTermIndex.count);
00814 mTermIndex.ref->write(HEADER_USED, mTermIndex.used);
00815 mTermIndex.ref->write(HEADER_INDEXED, mTermIndex.indexed);
00816 mTermIndex.ref->write(HEADER_REMOVED, mTermIndex.removed);
00817 syncIndex();
00818 return TRUE;
00819 }
00820
00821
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
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
00887 bool
00888 KMMsgIndex::canHandleQuery(KMSearchRule *rule)
00889 {
00890 if(mIndexState == INDEX_RESTORE)
00891 restoreState();
00892 if(mIndexState != INDEX_IDLE)
00893 return FALSE;
00894 if(rule->field().isEmpty() || rule->contents().isEmpty())
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>") {
00904 return FALSE;
00905 }
00906 } else if(isKillHeader(rule->field().data(), rule->field().length())) {
00907 return FALSE;
00908 }
00909 QString match = rule->contents().lower();
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
00928 bool
00929 KMMsgIndex::canHandleQuery(KMSearchPattern *pat)
00930 {
00931 if(mIndexState == INDEX_RESTORE)
00932 restoreState();
00933 if(mIndexState != INDEX_IDLE)
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
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
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
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)) {
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
01092
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))
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>();
01135 }
01136
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
01147
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
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;
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) {
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"