kmail

kmfoldermbox.cpp
1/*
2 * kmail: KDE mail client
3 * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#include <config.h>
21#include <tqfileinfo.h>
22#include <tqregexp.h>
23
24#include "kmfoldermbox.h"
25#include "folderstorage.h"
26#include "kmfolder.h"
27#include "kmkernel.h"
28#include "kmmsgdict.h"
29#include "undostack.h"
30#include "kcursorsaver.h"
31#include "jobscheduler.h"
32#include "compactionjob.h"
33#include "util.h"
34
35#include <kdebug.h>
36#include <tdelocale.h>
37#include <tdemessagebox.h>
38#include <knotifyclient.h>
39#include <kprocess.h>
40#include <tdeconfig.h>
41
42#include <ctype.h>
43#include <stdio.h>
44#include <errno.h>
45#include <assert.h>
46#include <ctype.h>
47#include <unistd.h>
48
49#include <fcntl.h>
50
51#include <stdlib.h>
52#include <sys/types.h>
53#include <sys/stat.h>
54#include <sys/file.h>
55#include "broadcaststatus.h"
56using KPIM::BroadcastStatus;
57
58#ifndef MAX_LINE
59#define MAX_LINE 4096
60#endif
61#ifndef INIT_MSGS
62#define INIT_MSGS 8
63#endif
64
65// Regular expression to find the line that seperates messages in a mail
66// folder:
67#define MSG_SEPERATOR_START "From "
68#define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1)
69#define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]"
70
71
72//-----------------------------------------------------------------------------
73KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name)
74 : KMFolderIndex(folder, name)
75{
76 mStream = 0;
77 mFilesLocked = false;
78 mReadOnly = false;
79 mLockType = lock_none;
80}
81
82
83//-----------------------------------------------------------------------------
84KMFolderMbox::~KMFolderMbox()
85{
86 if (mOpenCount>0)
87 close("~kmfoldermbox", true);
88 if (kmkernel->undoStack())
89 kmkernel->undoStack()->folderDestroyed( folder() );
90}
91
92//-----------------------------------------------------------------------------
93int KMFolderMbox::open(const char *owner)
94{
95 Q_UNUSED( owner );
96 int rc = 0;
97
98 mOpenCount++;
99 kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
100
101 if (mOpenCount > 1) return 0; // already open
102
103 assert(!folder()->name().isEmpty());
104
105 mFilesLocked = false;
106 mStream = fopen(TQFile::encodeName(location()), "r+"); // messages file
107 if (!mStream)
108 {
109 KNotifyClient::event( 0, "warning",
110 i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno)));
111 kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl;
112 mOpenCount = 0;
113 return errno;
114 }
115
116 lock();
117
118 if (!folder()->path().isEmpty())
119 {
121 // test if index file exists and is up-to-date
122 if (KMFolderIndex::IndexOk != index_status)
123 {
124 // only show a warning if the index file exists, otherwise it can be
125 // silently regenerated
126 if (KMFolderIndex::IndexTooOld == index_status) {
127 TQString msg = i18n("<qt><p>The index of folder '%2' seems "
128 "to be out of date. To prevent message "
129 "corruption the index will be "
130 "regenerated. As a result deleted "
131 "messages might reappear and status "
132 "flags might be lost.</p>"
133 "<p>Please read the corresponding entry "
134 "in the <a href=\"%1\">FAQ section of the manual "
135 "of KMail</a> for "
136 "information about how to prevent this "
137 "problem from happening again.</p></qt>")
138 .arg("help:/kmail/faq.html#faq-index-regeneration")
139 .arg(name());
140 // When KMail is starting up we have to show a non-blocking message
141 // box so that the initialization can continue. We don't show a
142 // queued message box when KMail isn't starting up because queued
143 // message boxes don't have a "Don't ask again" checkbox.
144 if (kmkernel->startingUp())
145 {
146 TDEConfigGroup configGroup( KMKernel::config(), "Notification Messages" );
147 bool showMessage =
148 configGroup.readBoolEntry( "showIndexRegenerationMessage", true );
149 if (showMessage)
150 KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
151 msg, i18n("Index Out of Date"),
152 KMessageBox::AllowLink );
153 }
154 else
155 {
156 KCursorSaver idle(KBusyPtr::idle());
157 KMessageBox::information( 0, msg, i18n("Index Out of Date"),
158 "showIndexRegenerationMessage",
159 KMessageBox::AllowLink );
160 }
161 }
162 TQString str;
163 mIndexStream = 0;
164 str = i18n("Folder `%1' changed. Recreating index.")
165 .arg(name());
166 emit statusMsg(str);
167 } else {
168 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "r+"); // index file
169 if ( mIndexStream ) {
170 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
171 updateIndexStreamPtr();
172 }
173 }
174
175 if (!mIndexStream)
177 else
178 if (!readIndex())
180 }
181 else
182 {
183 mAutoCreateIndex = false;
185 }
186
187 mChanged = false;
188
189 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
190 if (mIndexStream)
191 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
192
193 return rc;
194}
195
196//----------------------------------------------------------------------------
198{
199 assert(!folder()->name().isEmpty());
200
201 if (access(TQFile::encodeName(location()), R_OK | W_OK) != 0) {
202 kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl;
203 return 1;
204 }
205 return 0;
206}
207
208//-----------------------------------------------------------------------------
210{
211 int rc;
212 int old_umask;
213
214 assert(!folder()->name().isEmpty());
215 assert(mOpenCount == 0);
216
217 kdDebug(5006) << "Creating folder " << name() << endl;
218 if (access(TQFile::encodeName(location()), F_OK) == 0) {
219 kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl;
220 kdDebug(5006) << "File:: " << endl;
221 kdDebug(5006) << "Error " << endl;
222 return EEXIST;
223 }
224
225 old_umask = umask(077);
226 mStream = fopen(TQFile::encodeName(location()), "w+"); //sven; open RW
227 umask(old_umask);
228
229 if (!mStream) return errno;
230
231 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
232
233 if (!folder()->path().isEmpty())
234 {
235 old_umask = umask(077);
236 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "w+"); //sven; open RW
237 updateIndexStreamPtr(true);
238 umask(old_umask);
239
240 if (!mIndexStream) return errno;
241 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
242 }
243 else
244 {
245 mAutoCreateIndex = false;
246 }
247
248 mOpenCount++;
249 mChanged = false;
250
251 rc = writeIndex();
252 if (!rc) lock();
253 return rc;
254}
255
256
257//-----------------------------------------------------------------------------
258void KMFolderMbox::reallyDoClose(const char* owner)
259{
260 Q_UNUSED( owner );
262 {
263 if (KMFolderIndex::IndexOk != indexStatus()) {
264 kdDebug(5006) << "Critical error: " << location() <<
265 " has been modified by an external application while KMail was running." << endl;
266 // exit(1); backed out due to broken nfs
267 }
268
269 updateIndex();
270 writeConfig();
271 }
272
273 if (!noContent()) {
274 if (mStream) unlock();
275 mMsgList.clear(true);
276
277 if (mStream) fclose(mStream);
278 if (mIndexStream) {
279 fclose(mIndexStream);
280 updateIndexStreamPtr(true);
281 }
282 }
283 mOpenCount = 0;
284 mStream = 0;
285 mIndexStream = 0;
286 mFilesLocked = false;
287 mUnreadMsgs = -1;
288
289 mMsgList.reset(INIT_MSGS);
290}
291
292//-----------------------------------------------------------------------------
294{
295 if (mOpenCount > 0)
296 if (!mStream || fsync(fileno(mStream)) ||
297 !mIndexStream || fsync(fileno(mIndexStream))) {
298 kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? TQString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug.")));
299 }
300}
301
302//-----------------------------------------------------------------------------
303int KMFolderMbox::lock()
304{
305 int rc;
306 struct flock fl;
307 fl.l_type=F_WRLCK;
308 fl.l_whence=0;
309 fl.l_start=0;
310 fl.l_len=0;
311 fl.l_pid=-1;
312 TQCString cmd_str;
313 assert(mStream != 0);
314 mFilesLocked = false;
315 mReadOnly = false;
316
317 switch( mLockType )
318 {
319 case FCNTL:
320 rc = fcntl(fileno(mStream), F_SETLKW, &fl);
321
322 if (rc < 0)
323 {
324 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
325 << strerror(errno) << " (" << errno << ")" << endl;
326 mReadOnly = true;
327 return errno;
328 }
329
330 if (mIndexStream)
331 {
332 rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
333
334 if (rc < 0)
335 {
336 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
337 << strerror(errno) << " (" << errno << ")" << endl;
338 rc = errno;
339 fl.l_type = F_UNLCK;
340 /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl);
341 mReadOnly = true;
342 return rc;
343 }
344 }
345 break;
346
347 case procmail_lockfile:
348 cmd_str = "lockfile -l20 -r5 ";
349 if (!mProcmailLockFileName.isEmpty())
350 cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
351 else
352 cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
353
354 rc = system( cmd_str.data() );
355 if( rc != 0 )
356 {
357 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
358 << strerror(rc) << " (" << rc << ")" << endl;
359 mReadOnly = true;
360 return rc;
361 }
362 if( mIndexStream )
363 {
364 cmd_str = "lockfile -l20 -r5 " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
365 rc = system( cmd_str.data() );
366 if( rc != 0 )
367 {
368 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
369 << strerror(rc) << " (" << rc << ")" << endl;
370 mReadOnly = true;
371 return rc;
372 }
373 }
374 break;
375
376 case mutt_dotlock:
377 cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(location()));
378 rc = system( cmd_str.data() );
379 if( rc != 0 )
380 {
381 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
382 << strerror(rc) << " (" << rc << ")" << endl;
383 mReadOnly = true;
384 return rc;
385 }
386 if( mIndexStream )
387 {
388 cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
389 rc = system( cmd_str.data() );
390 if( rc != 0 )
391 {
392 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
393 << strerror(rc) << " (" << rc << ")" << endl;
394 mReadOnly = true;
395 return rc;
396 }
397 }
398 break;
399
400 case mutt_dotlock_privileged:
401 cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(location()));
402 rc = system( cmd_str.data() );
403 if( rc != 0 )
404 {
405 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
406 << strerror(rc) << " (" << rc << ")" << endl;
407 mReadOnly = true;
408 return rc;
409 }
410 if( mIndexStream )
411 {
412 cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
413 rc = system( cmd_str.data() );
414 if( rc != 0 )
415 {
416 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
417 << strerror(rc) << " (" << rc << ")" << endl;
418 mReadOnly = true;
419 return rc;
420 }
421 }
422 break;
423
424 case lock_none:
425 default:
426 break;
427 }
428
429
430 mFilesLocked = true;
431 return 0;
432}
433
434//-------------------------------------------------------------
435FolderJob*
436KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
437 KMFolder *folder, TQString, const AttachmentStrategy* ) const
438{
439 MboxJob *job = new MboxJob( msg, jt, folder );
440 job->setParent( this );
441 return job;
442}
443
444//-------------------------------------------------------------
445FolderJob*
446KMFolderMbox::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets,
447 FolderJob::JobType jt, KMFolder *folder ) const
448{
449 MboxJob *job = new MboxJob( msgList, sets, jt, folder );
450 job->setParent( this );
451 return job;
452}
453
454//-----------------------------------------------------------------------------
455int KMFolderMbox::unlock()
456{
457 int rc;
458 struct flock fl;
459 fl.l_type=F_UNLCK;
460 fl.l_whence=0;
461 fl.l_start=0;
462 fl.l_len=0;
463 TQCString cmd_str;
464
465 assert(mStream != 0);
466 mFilesLocked = false;
467
468 switch( mLockType )
469 {
470 case FCNTL:
471 if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl);
472 fcntl(fileno(mStream), F_SETLK, &fl);
473 rc = errno;
474 break;
475
476 case procmail_lockfile:
477 cmd_str = "rm -f ";
478 if (!mProcmailLockFileName.isEmpty())
479 cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
480 else
481 cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
482
483 rc = system( cmd_str.data() );
484 if( mIndexStream )
485 {
486 cmd_str = "rm -f " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
487 rc = system( cmd_str.data() );
488 }
489 break;
490
491 case mutt_dotlock:
492 cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(location()));
493 rc = system( cmd_str.data() );
494 if( mIndexStream )
495 {
496 cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
497 rc = system( cmd_str.data() );
498 }
499 break;
500
501 case mutt_dotlock_privileged:
502 cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(location()));
503 rc = system( cmd_str.data() );
504 if( mIndexStream )
505 {
506 cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
507 rc = system( cmd_str.data() );
508 }
509 break;
510
511 case lock_none:
512 default:
513 rc = 0;
514 break;
515 }
516
517 return rc;
518}
519
520
521//-----------------------------------------------------------------------------
523{
524 if ( !mCompactable )
525 return KMFolderIndex::IndexCorrupt;
526
527 TQFileInfo contInfo(location());
528 TQFileInfo indInfo(indexLocation());
529
530 if (!contInfo.exists()) return KMFolderIndex::IndexOk;
531 if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
532
533 // Check whether the mbox file is more than 5 seconds newer than the index
534 // file. The 5 seconds are added to reduce the number of false alerts due
535 // to slightly out of sync clocks of the NFS server and the local machine.
536 return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
537 ? KMFolderIndex::IndexTooOld
538 : KMFolderIndex::IndexOk;
539}
540
541
542//-----------------------------------------------------------------------------
544{
545 char line[MAX_LINE];
546 char status[8], xstatus[8];
547 TQCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
548 TQCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
549 TQCString sizeServerStr, uidStr;
550 TQCString contentTypeStr, charset;
551 bool atEof = false;
552 bool inHeader = true;
553 KMMsgInfo* mi;
554 TQString msgStr;
555 TQRegExp regexp(MSG_SEPERATOR_REGEX);
556 int i, num, numStatus;
557 short needStatus;
558
559 assert(mStream != 0);
560 rewind(mStream);
561
562 mMsgList.clear();
563
564 num = -1;
565 numStatus= 11;
566 off_t offs = 0;
567 size_t size = 0;
568 dateStr = "";
569 fromStr = "";
570 toStr = "";
571 subjStr = "";
572 *status = '\0';
573 *xstatus = '\0';
574 xmarkStr = "";
575 replyToIdStr = "";
576 replyToAuxIdStr = "";
577 referencesStr = "";
578 msgIdStr = "";
579 needStatus = 3;
580 size_t sizeServer = 0;
581 ulong uid = 0;
582
583
584 while (!atEof)
585 {
586 off_t pos = ftell(mStream);
587 if (!fgets(line, MAX_LINE, mStream)) atEof = true;
588
589 if (atEof ||
590 (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 &&
591 regexp.search(line) >= 0))
592 {
593 size = pos - offs;
594 pos = ftell(mStream);
595
596 if (num >= 0)
597 {
598 if (numStatus <= 0)
599 {
600 msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num);
601 emit statusMsg(msgStr);
602 numStatus = 10;
603 }
604
605 if (size > 0)
606 {
607 msgIdStr = msgIdStr.stripWhiteSpace();
608 if( !msgIdStr.isEmpty() ) {
609 int rightAngle;
610 rightAngle = msgIdStr.find( '>' );
611 if( rightAngle != -1 )
612 msgIdStr.truncate( rightAngle + 1 );
613 }
614
615 replyToIdStr = replyToIdStr.stripWhiteSpace();
616 if( !replyToIdStr.isEmpty() ) {
617 int rightAngle;
618 rightAngle = replyToIdStr.find( '>' );
619 if( rightAngle != -1 )
620 replyToIdStr.truncate( rightAngle + 1 );
621 }
622
623 referencesStr = referencesStr.stripWhiteSpace();
624 if( !referencesStr.isEmpty() ) {
625 int leftAngle, rightAngle;
626 leftAngle = referencesStr.findRev( '<' );
627 if( ( leftAngle != -1 )
628 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
629 // use the last reference, instead of missing In-Reply-To
630 replyToIdStr = referencesStr.mid( leftAngle );
631 }
632
633 // find second last reference
634 leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
635 if( leftAngle != -1 )
636 referencesStr = referencesStr.mid( leftAngle );
637 rightAngle = referencesStr.findRev( '>' );
638 if( rightAngle != -1 )
639 referencesStr.truncate( rightAngle + 1 );
640
641 // Store the second to last reference in the replyToAuxIdStr
642 // It is a good candidate for threading the message below if the
643 // message In-Reply-To points to is not kept in this folder,
644 // but e.g. in an Outbox
645 replyToAuxIdStr = referencesStr;
646 rightAngle = referencesStr.find( '>' );
647 if( rightAngle != -1 )
648 replyToAuxIdStr.truncate( rightAngle + 1 );
649 }
650
651 contentTypeStr = contentTypeStr.stripWhiteSpace();
652 charset = "";
653 if ( !contentTypeStr.isEmpty() )
654 {
655 int cidx = contentTypeStr.find( "charset=" );
656 if ( cidx != -1 ) {
657 charset = contentTypeStr.mid( cidx + 8 );
658 if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
659 charset = charset.mid( 1 );
660 }
661 cidx = 0;
662 while ( (unsigned int) cidx < charset.length() ) {
663 if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
664 charset[cidx] != '-' && charset[cidx] != '_' ) )
665 break;
666 ++cidx;
667 }
668 charset.truncate( cidx );
669 // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
670 // charset << " from " << contentTypeStr << endl;
671 }
672 }
673
674 mi = new KMMsgInfo(folder());
675 mi->init( subjStr.stripWhiteSpace(),
676 fromStr.stripWhiteSpace(),
677 toStr.stripWhiteSpace(),
678 0, KMMsgStatusNew,
679 xmarkStr.stripWhiteSpace(),
680 replyToIdStr, replyToAuxIdStr, msgIdStr,
681 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
682 KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid );
683 mi->setStatus(status, xstatus);
684 mi->setDate( dateStr.stripWhiteSpace().data() );
685 mi->setDirty(false);
686 mMsgList.append(mi, mExportsSernums );
687
688 *status = '\0';
689 *xstatus = '\0';
690 needStatus = 3;
691 xmarkStr = "";
692 replyToIdStr = "";
693 replyToAuxIdStr = "";
694 referencesStr = "";
695 msgIdStr = "";
696 dateStr = "";
697 fromStr = "";
698 subjStr = "";
699 sizeServer = 0;
700 uid = 0;
701 }
702 else num--,numStatus++;
703 }
704
705 offs = ftell(mStream);
706 num++;
707 numStatus--;
708 inHeader = true;
709 continue;
710 }
711 // Is this a long header line?
712 if (inHeader && (line[0]=='\t' || line[0]==' '))
713 {
714 i = 0;
715 while (line [i]=='\t' || line [i]==' ') i++;
716 if (line [i] < ' ' && line [i]>0) inHeader = false;
717 else if (lastStr) *lastStr += line + i;
718 }
719 else lastStr = 0;
720
721 if (inHeader && (line [0]=='\n' || line [0]=='\r'))
722 inHeader = false;
723 if (!inHeader) continue;
724
725 /* -sanders Make all messages read when auto-recreating index */
726 /* Reverted, as it breaks reading the sent mail status, for example.
727 -till */
728 if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0)
729 {
730 for(i=0; i<4 && line[i+8] > ' '; i++)
731 status[i] = line[i+8];
732 status[i] = '\0';
733 needStatus &= ~1;
734 }
735 else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0)
736 {
737 for(i=0; i<4 && line[i+10] > ' '; i++)
738 xstatus[i] = line[i+10];
739 xstatus[i] = '\0';
740 needStatus &= ~2;
741 }
742 else if (strncasecmp(line,"X-KMail-Mark:",13)==0)
743 xmarkStr = TQCString(line+13);
744 else if (strncasecmp(line,"In-Reply-To:",12)==0) {
745 replyToIdStr = TQCString(line+12);
746 lastStr = &replyToIdStr;
747 }
748 else if (strncasecmp(line,"References:",11)==0) {
749 referencesStr = TQCString(line+11);
750 lastStr = &referencesStr;
751 }
752 else if (strncasecmp(line,"Message-Id:",11)==0) {
753 msgIdStr = TQCString(line+11);
754 lastStr = &msgIdStr;
755 }
756 else if (strncasecmp(line,"Date:",5)==0)
757 {
758 dateStr = TQCString(line+5);
759 lastStr = &dateStr;
760 }
761 else if (strncasecmp(line,"From:", 5)==0)
762 {
763 fromStr = TQCString(line+5);
764 lastStr = &fromStr;
765 }
766 else if (strncasecmp(line,"To:", 3)==0)
767 {
768 toStr = TQCString(line+3);
769 lastStr = &toStr;
770 }
771 else if (strncasecmp(line,"Subject:",8)==0)
772 {
773 subjStr = TQCString(line+8);
774 lastStr = &subjStr;
775 }
776 else if (strncasecmp(line,"X-Length:",9)==0)
777 {
778 sizeServerStr = TQCString(line+9);
779 sizeServer = sizeServerStr.toULong();
780 lastStr = &sizeServerStr;
781 }
782 else if (strncasecmp(line,"X-UID:",6)==0)
783 {
784 uidStr = TQCString(line+6);
785 uid = uidStr.toULong();
786 lastStr = &uidStr;
787 }
788 else if (strncasecmp(line, "Content-Type:", 13) == 0)
789 {
790 contentTypeStr = TQCString(line+13);
791 lastStr = &contentTypeStr;
792 }
793 }
794
796 {
797 emit statusMsg(i18n("Writing index file"));
798 writeIndex();
799 }
800 else mHeaderOffset = 0;
801
803
804 if (kmkernel->outboxFolder() == folder() && count() > 0)
805 KMessageBox::queuedMessageBox(0, KMessageBox::Information,
806 i18n("Your outbox contains messages which were "
807 "most-likely not created by KMail;\nplease remove them from there if you "
808 "do not want KMail to send them."));
809
811 return 0;
812}
813
814
815//-----------------------------------------------------------------------------
816KMMessage* KMFolderMbox::readMsg(int idx)
817{
818 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
819
820 assert(mi!=0 && !mi->isMessage());
821 assert(mStream != 0);
822
823 KMMessage *msg = new KMMessage(*mi);
824 msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
825 mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
826 msg->fromDwString(getDwString(idx));
827 return msg;
828}
829
830
831#define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
832// performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
833static size_t unescapeFrom( char* str, size_t strLen ) {
834 if ( !str )
835 return 0;
836 if ( strLen <= STRDIM(">From ") )
837 return strLen;
838
839 // yes, *d++ = *s++ is a no-op as long as d == s (until after the
840 // first >From_), but writes are cheap compared to reads and the
841 // data is already in the cache from the read, so special-casing
842 // might even be slower...
843 const char * s = str;
844 char * d = str;
845 const char * const e = str + strLen - STRDIM(">From ");
846
847 while ( s < e ) {
848 if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
849 *d++ = *s++; // == '\n'
850 *d++ = *s++; // == '>'
851 while ( s < e && *s == '>' )
852 *d++ = *s++;
853 if ( tqstrncmp( s, "From ", STRDIM("From ") ) == 0 )
854 --d;
855 }
856 *d++ = *s++; // yes, s might be e here, but e is not the end :-)
857 }
858 // copy the rest:
859 while ( s < str + strLen )
860 *d++ = *s++;
861 if ( d < s ) // only NUL-terminate if it's shorter
862 *d = 0;
863
864 return d - str;
865}
866
867//static
868TQByteArray KMFolderMbox::escapeFrom( const DwString & str ) {
869 const unsigned int strLen = str.length();
870 if ( strLen <= STRDIM("From ") )
871 return KMail::Util::ByteArray( str );
872 // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
873 TQByteArray result( int( strLen + 5 ) / 6 * 7 + 1 );
874
875 const char * s = str.data();
876 const char * const e = s + strLen - STRDIM("From ");
877 char * d = result.data();
878
879 bool onlyAnglesAfterLF = false; // dont' match ^From_
880 while ( s < e ) {
881 switch ( *s ) {
882 case '\n':
883 onlyAnglesAfterLF = true;
884 break;
885 case '>':
886 break;
887 case 'F':
888 if ( onlyAnglesAfterLF && tqstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
889 *d++ = '>';
890 // fall through
891 default:
892 onlyAnglesAfterLF = false;
893 break;
894 }
895 *d++ = *s++;
896 }
897 while ( s < str.data() + strLen )
898 *d++ = *s++;
899
900 result.truncate( d - result.data() );
901 return result;
902}
903
904#undef STRDIM
905
906//-----------------------------------------------------------------------------
907DwString KMFolderMbox::getDwString(int idx)
908{
909 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
910
911 assert(mi!=0);
912 assert(mStream != 0);
913
914 size_t msgSize = mi->msgSize();
915 char* msgText = new char[ msgSize + 1 ];
916
917 fseek(mStream, mi->folderOffset(), SEEK_SET);
918 fread(msgText, msgSize, 1, mStream);
919 msgText[msgSize] = '\0';
920
921 size_t newMsgSize = unescapeFrom( msgText, msgSize );
922 newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize );
923
924 DwString msgStr;
925 // the DwString takes possession of msgText, so we must not delete msgText
926 msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
927 return msgStr;
928}
929
930
931//-----------------------------------------------------------------------------
932int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret )
933{
934 if (!canAddMsgNow(aMsg, aIndex_ret)) return 0;
935 TQByteArray msgText;
936 char endStr[3];
937 int idx = -1, rc;
938 KMFolder* msgParent;
939 bool editing = false;
940 int growth = 0;
941
942 KMFolderOpener openThis(folder(), "mboxaddMsg");
943 rc = openThis.openResult();
944 if (rc)
945 {
946 kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl;
947 return rc;
948 }
949
950 // take message out of the folder it is currently in, if any
951 msgParent = aMsg->parent();
952 if (msgParent)
953 {
954 if ( msgParent== folder() )
955 {
956 if (kmkernel->folderIsDraftOrOutbox( folder() ))
957 //special case for Edit message.
958 {
959 kdDebug(5006) << "Editing message in outbox or drafts" << endl;
960 editing = true;
961 }
962 else
963 return 0;
964 }
965
966 idx = msgParent->find(aMsg);
967 msgParent->getMsg( idx );
968 }
969
970 if (folderType() != KMFolderTypeImap)
971 {
972/*
973TQFile fileD0( "testdat_xx-kmfoldermbox-0" );
974if( fileD0.open( IO_WriteOnly ) ) {
975 TQDataStream ds( &fileD0 );
976 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
977 fileD0.close(); // If data is 0 we just create a zero length file.
978}
979*/
980 aMsg->setStatusFields();
981/*
982TQFile fileD1( "testdat_xx-kmfoldermbox-1" );
983if( fileD1.open( IO_WriteOnly ) ) {
984 TQDataStream ds( &fileD1 );
985 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
986 fileD1.close(); // If data is 0 we just create a zero length file.
987}
988*/
989 if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by
990 aMsg->removeHeaderField("Content-Type"); // the line above
991 }
992 msgText = escapeFrom( aMsg->asDwString() );
993 size_t len = msgText.size();
994
995 assert(mStream != 0);
996 clearerr(mStream);
997 if (len <= 0)
998 {
999 kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
1000 return 0;
1001 }
1002
1003 // Make sure the file is large enough to check for an end
1004 // character
1005 fseek(mStream, 0, SEEK_END);
1006 off_t revert = ftell(mStream);
1007 if (ftell(mStream) >= 2) {
1008 // write message to folder file
1009 fseek(mStream, -2, SEEK_END);
1010 fread(endStr, 1, 2, mStream); // ensure separating empty line
1011 if (ftell(mStream) > 0 && endStr[0]!='\n') {
1012 ++growth;
1013 if (endStr[1]!='\n') {
1014 //printf ("****endStr[1]=%c\n", endStr[1]);
1015 fwrite("\n\n", 1, 2, mStream);
1016 ++growth;
1017 }
1018 else fwrite("\n", 1, 1, mStream);
1019 }
1020 }
1021 fseek(mStream,0,SEEK_END); // this is needed on solaris and others
1022 int error = ferror(mStream);
1023 if (error)
1024 return error;
1025
1026 TQCString messageSeparator( aMsg->mboxMessageSeparator() );
1027 fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream );
1028 off_t offs = ftell(mStream);
1029 fwrite(msgText.data(), len, 1, mStream);
1030 if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream);
1031 fflush(mStream);
1032 size_t size = ftell(mStream) - offs;
1033
1034 error = ferror(mStream);
1035 if (error) {
1036 kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl;
1037 if (ftell(mStream) > revert) {
1038 kdDebug(5006) << "Undoing changes" << endl;
1039 truncate( TQFile::encodeName(location()), revert );
1040 }
1041 kmkernel->emergencyExit( i18n("Could not add message to folder: ") + TQString::fromLocal8Bit(strerror(errno)));
1042
1043 /* This code is not 100% reliable
1044 bool busy = kmkernel->kbp()->isBusy();
1045 if (busy) kmkernel->kbp()->idle();
1046 KMessageBox::sorry(0,
1047 i18n("Unable to add message to folder.\n"
1048 "(No space left on device or insufficient quota?)\n"
1049 "Free space and sufficient quota are required to continue safely."));
1050 if (busy) kmkernel->kbp()->busy();
1051 kmkernel->kbp()->idle();
1052 */
1053 return error;
1054 }
1055
1056 if (msgParent) {
1057 if (idx >= 0) msgParent->take(idx);
1058 }
1059// if (mAccount) aMsg->removeHeaderField("X-UID");
1060
1061 if (aMsg->isUnread() || aMsg->isNew() ||
1062 (folder() == kmkernel->outboxFolder())) {
1063 if (mUnreadMsgs == -1) mUnreadMsgs = 1;
1064 else ++mUnreadMsgs;
1065 if ( !mQuiet )
1066 emit numUnreadMsgsChanged( folder() );
1067 }
1068 ++mTotalMsgs;
1069 mSize = -1;
1070
1071 if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) {
1072 aMsg->updateAttachmentState();
1073 }
1074 if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
1075 aMsg->updateInvitationState();
1076 }
1077
1078 // store information about the position in the folder file in the message
1079 aMsg->setParent(folder());
1080 aMsg->setFolderOffset(offs);
1081 aMsg->setMsgSize(size);
1082 idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums );
1083 if ( aMsg->getMsgSerNum() <= 0 )
1084 aMsg->setMsgSerNum();
1085 else
1086 replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
1087
1088 // change the length of the previous message to encompass white space added
1089 if ((idx > 0) && (growth > 0)) {
1090 // don't grow if a deleted message claims space at the end of the file
1091 if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() )
1092 mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
1093 }
1094
1095 // write index entry if desired
1096 if (mAutoCreateIndex)
1097 {
1098 assert(mIndexStream != 0);
1099 clearerr(mIndexStream);
1100 fseek(mIndexStream, 0, SEEK_END);
1101 revert = ftell(mIndexStream);
1102
1103 KMMsgBase * mb = &aMsg->toMsgBase();
1104 int len;
1105 const uchar *buffer = mb->asIndexString(len);
1106 fwrite(&len,sizeof(len), 1, mIndexStream);
1107 mb->setIndexOffset( ftell(mIndexStream) );
1108 mb->setIndexLength( len );
1109 if(fwrite(buffer, len, 1, mIndexStream) != 1)
1110 kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
1111
1112 fflush(mIndexStream);
1113 error = ferror(mIndexStream);
1114
1115 if ( mExportsSernums )
1116 error |= appendToFolderIdsFile( idx );
1117
1118 if (error) {
1119 kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
1120 if (ftell(mIndexStream) > revert) {
1121 kdWarning(5006) << "Undoing changes" << endl;
1122 truncate( TQFile::encodeName(indexLocation()), revert );
1123 }
1124 if ( errno )
1125 kmkernel->emergencyExit( i18n("Could not add message to folder:") + TQString::fromLocal8Bit(strerror(errno)));
1126 else
1127 kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
1128
1129 /* This code may not be 100% reliable
1130 bool busy = kmkernel->kbp()->isBusy();
1131 if (busy) kmkernel->kbp()->idle();
1132 KMessageBox::sorry(0,
1133 i18n("Unable to add message to folder.\n"
1134 "(No space left on device or insufficient quota?)\n"
1135 "Free space and sufficient quota are required to continue safely."));
1136 if (busy) kmkernel->kbp()->busy();
1137 */
1138 return error;
1139 }
1140 }
1141
1142 if (aIndex_ret) *aIndex_ret = idx;
1144
1145 // All streams have been flushed without errors if we arrive here
1146 // Return success!
1147 // (Don't return status of stream, it may have been closed already.)
1148 return 0;
1149}
1150
1151int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done )
1152{
1153 int rc = 0;
1154 TQCString mtext;
1155 unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
1156 TQMIN( mMsgList.count(), startIndex + nbMessages );
1157 //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl;
1158 for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
1159 KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
1160 size_t msize = mi->msgSize();
1161 if (mtext.size() < msize + 2)
1162 mtext.resize(msize+2);
1163 off_t folder_offset = mi->folderOffset();
1164
1165 //now we need to find the separator! grr...
1166 for(off_t i = folder_offset-25; true; i -= 20) {
1167 off_t chunk_offset = i <= 0 ? 0 : i;
1168 if(fseek(mStream, chunk_offset, SEEK_SET) == -1) {
1169 rc = errno;
1170 break;
1171 }
1172 if (mtext.size() < 20)
1173 mtext.resize(20);
1174 fread(mtext.data(), 20, 1, mStream);
1175 if(i <= 0) { //woops we've reached the top of the file, last try..
1176 if ( mtext.contains( "from ", false ) ) {
1177 if (mtext.size() < (size_t)folder_offset)
1178 mtext.resize(folder_offset);
1179 if(fseek(mStream, chunk_offset, SEEK_SET) == -1 ||
1180 !fread(mtext.data(), folder_offset, 1, mStream) ||
1181 !fwrite(mtext.data(), folder_offset, 1, tmpfile)) {
1182 rc = errno;
1183 break;
1184 }
1185 offs += folder_offset;
1186 } else {
1187 rc = 666;
1188 }
1189 break;
1190 } else {
1191 int last_crlf = -1;
1192 for(int i2 = 0; i2 < 20; i2++) {
1193 if(*(mtext.data()+i2) == '\n')
1194 last_crlf = i2;
1195 }
1196 if(last_crlf != -1) {
1197 int size = folder_offset - (i + last_crlf+1);
1198 if ((int)mtext.size() < size)
1199 mtext.resize(size);
1200 if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 ||
1201 !fread(mtext.data(), size, 1, mStream) ||
1202 !fwrite(mtext.data(), size, 1, tmpfile)) {
1203 rc = errno;
1204 break;
1205 }
1206 offs += size;
1207 break;
1208 }
1209 }
1210 }
1211 if (rc)
1212 break;
1213
1214 //now actually write the message
1215 if(fseek(mStream, folder_offset, SEEK_SET) == -1 ||
1216 !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) {
1217 rc = errno;
1218 break;
1219 }
1220 mi->setFolderOffset(offs);
1221 offs += msize;
1222 }
1223 done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors
1224 return rc;
1225}
1226
1227//-----------------------------------------------------------------------------
1228int KMFolderMbox::compact( bool silent )
1229{
1230 // This is called only when the user explicitely requests compaction,
1231 // so we don't check needsCompact.
1232
1233 KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ );
1234 int rc = job->executeNow( silent );
1235 // Note that job autodeletes itself.
1236
1237 // If this is the current folder, the changed signal will ultimately call
1238 // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it
1239 TQString statusMsg = BroadcastStatus::instance()->statusMsg();
1240 emit changed();
1241 BroadcastStatus::instance()->setStatusMsg( statusMsg );
1242 return rc;
1243}
1244
1245
1246//-----------------------------------------------------------------------------
1247void KMFolderMbox::setLockType( LockType ltype )
1248{
1249 mLockType = ltype;
1250}
1251
1252//-----------------------------------------------------------------------------
1253void KMFolderMbox::setProcmailLockFileName( const TQString &fname )
1254{
1255 mProcmailLockFileName = fname;
1256}
1257
1258//-----------------------------------------------------------------------------
1260{
1261 int rc = 0;
1262 rc = unlink(TQFile::encodeName(location()));
1263 return rc;
1264}
1265
1266//-----------------------------------------------------------------------------
1268{
1269 int rc = 0;
1270 if (truncate(TQFile::encodeName(location()), 0))
1271 rc = errno;
1272 return rc;
1273}
1274
1275//-----------------------------------------------------------------------------
1276/*virtual*/
1277TQ_INT64 KMFolderMbox::doFolderSize() const
1278{
1279 TQFileInfo info( location() );
1280 return (TQ_INT64)(info.size());
1281}
1282
1283//-----------------------------------------------------------------------------
1284#include "kmfoldermbox.moc"
virtual int create()=0
Create a new folder with the name of this object and open it.
void emitMsgAddedSignals(int idx)
Called by derived classes implementation of addMsg.
int appendToFolderIdsFile(int idx=-1)
Append message to end of message serial number file.
void statusMsg(const TQString &)
Emmited to display a message somewhere in a status line.
void numUnreadMsgsChanged(KMFolder *)
Emitted when number of unread messages has changed.
void replaceMsgSerNum(unsigned long sernum, KMMsgBase *msg, int idx)
Replaces the serial number for the message msg at index idx with sernum.
void changed()
Emitted when the status, name, or associated accounts of this folder changed.
void invalidateFolder()
Called when serial numbers for a folder are invalidated, invalidates/recreates data structures depend...
bool mExportsSernums
Has this storage exported its serial numbers to the global message dict for lookup?
virtual void correctUnreadMsgsCount()
A cludge to help make sure the count of unread messges is kept in sync.
virtual KMFolderType folderType() const
Returns the type of this folder.
virtual int expungeContents()=0
Called by KMFolder::expunge() to delete the actual contents.
virtual bool canAddMsgNow(KMMessage *aMsg, int *aIndex_ret)
Returns false, if the message has to be retrieved from an IMAP account first.
void close(const char *owner, bool force=false)
Close folder.
virtual int removeContents()=0
Called by KMFolder::remove() to delete the actual contents.
virtual void writeConfig()
Write the config file.
virtual void sync()=0
fsync buffers to disk
virtual int canAccess()=0
Check folder for permissions Returns zero if readable and writable.
virtual bool noContent() const
Returns, if the folder can't contain mails, but only subfolder.
TQString label() const
Returns the label of the folder for visualization.
bool mCompactable
false if index file is out of sync with mbox file
TQString location() const
Returns full path to folder file.
int mUnreadMsgs
number of unread messages, -1 if not yet set
bool mAutoCreateIndex
is the automatic creation of a index file allowed ?
A FolderStorage with an index for faster access to often used message properties.
FILE * mIndexStream
table of contents file
virtual int writeIndex(bool createEmptyIndex=false)
Write index to index-file.
virtual TQString indexLocation() const
Returns full path to index file.
off_t mHeaderOffset
offset of header of index file
virtual int count(bool cache=false) const
Number of messages in this folder.
IndexStatus
This enum indicates the status of the index file.
virtual IndexStatus indexStatus()=0
Tests whether the contents of this folder is newer than the index.
virtual int updateIndex()
Incrementally update the index if possible else call writeIndex.
bool readIndex()
Read index file and fill the message-info list mMsgList.
virtual int createIndexFromContents()=0
Create index file from messages file and fill the message-info list mMsgList.
KMMsgList mMsgList
list of index entries or messages
Mail folder.
Definition kmfolder.h:69
KMMessage * take(int idx)
Detach message from this folder.
Definition kmfolder.cpp:380
KMMessage * getMsg(int idx)
Read message at given index.
Definition kmfolder.cpp:321
int find(const KMMsgBase *msg) const
Returns the index of the given message or -1 if not found.
Definition kmfolder.cpp:435
This is a Mime Message.
Definition kmmessage.h:68
bool readyToShow() const
Return if the message is ready to be shown.
Definition kmmessage.h:872
void setStatusFields()
Set "Status" and "X-Status" fields of the message from the internal message status.
void removeHeaderField(const TQCString &name)
Remove header field with given name.
void fromDwString(const DwString &str, bool setStatus=false)
Parse the string and create this message from it.
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition kmmessage.h:114
const DwString & asDwString() const
Return the entire message contents in the DwString.
void setMsgSerNum(unsigned long newMsgSerNum=0)
Sets the message serial number.
TQString headerField(const TQCString &name) const
Returns the value of a header field with the given name.
TQCString mboxMessageSeparator()
Returns an mbox message separator line for this message, i.e.
void setMsgInfo(KMMsgInfo *msgInfo)
Set the KMMsgInfo object corresponding to this message.
Definition kmmessage.h:932
TQByteArray ByteArray(const DwString &str)
Construct a TQByteArray from a DwString.
Definition util.cpp:122
size_t crlf2lf(char *str, const size_t strLen)
Convert all sequences of "\r\n" (carriage return followed by a line feed) to a single "\n" (line feed...
Definition util.cpp:44