kmail

kmfoldermaildir.cpp
1// kmfoldermaildir.cpp
2// Author: Kurt Granroth <granroth@kde.org>
3
4#ifdef HAVE_CONFIG_H
5#include <config.h>
6#endif
7
8#include <tqdir.h>
9#include <tqregexp.h>
10
11#include <libtdepim/tdefileio.h>
12#include "kmfoldermaildir.h"
13#include "kmfoldermgr.h"
14#include "kmfolder.h"
15#include "undostack.h"
16#include "maildirjob.h"
17#include "kcursorsaver.h"
18#include "jobscheduler.h"
19using KMail::MaildirJob;
20#include "compactionjob.h"
21#include "kmmsgdict.h"
22#include "util.h"
23
24#include <tdeapplication.h>
25#include <kdebug.h>
26#include <tdelocale.h>
27#include <kstaticdeleter.h>
28#include <tdemessagebox.h>
29#include <kdirsize.h>
30
31#include <dirent.h>
32#include <errno.h>
33#include <stdlib.h>
34#include <sys/stat.h>
35#include <sys/types.h>
36#include <unistd.h>
37#include <assert.h>
38#include <limits.h>
39#include <ctype.h>
40#include <fcntl.h>
41
42#ifndef MAX_LINE
43#define MAX_LINE 4096
44#endif
45#ifndef INIT_MSGS
46#define INIT_MSGS 8
47#endif
48
49// define the static member
50TQValueList<KMFolderMaildir::DirSizeJobQueueEntry> KMFolderMaildir::s_DirSizeJobQueue;
51
52//-----------------------------------------------------------------------------
53KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name)
54 : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false)
55{
56
57}
58
59
60//-----------------------------------------------------------------------------
61KMFolderMaildir::~KMFolderMaildir()
62{
63 if (mOpenCount>0) close("~foldermaildir", true);
64 if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
65}
66
67//-----------------------------------------------------------------------------
69{
70
71 assert(!folder()->name().isEmpty());
72
73 TQString sBadFolderName;
74 if (access(TQFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) {
75 sBadFolderName = location();
76 } else if (access(TQFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) {
77 sBadFolderName = location() + "/new";
78 } else if (access(TQFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) {
79 sBadFolderName = location() + "/cur";
80 } else if (access(TQFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) {
81 sBadFolderName = location() + "/tmp";
82 }
83
84 if ( !sBadFolderName.isEmpty() ) {
85 int nRetVal = TQFile::exists(sBadFolderName) ? EPERM : ENOENT;
86 KCursorSaver idle(KBusyPtr::idle());
87 if ( nRetVal == ENOENT )
88 KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.")
89 .arg(sBadFolderName));
90 else
91 KMessageBox::sorry(0, i18n("Error opening %1; either this is not a valid "
92 "maildir folder, or you do not have sufficient access permissions.")
93 .arg(sBadFolderName));
94 return nRetVal;
95 }
96
97 return 0;
98}
99
100//-----------------------------------------------------------------------------
101int KMFolderMaildir::open(const char *)
102{
103 int rc = 0;
104
105 mOpenCount++;
106 kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
107
108 if (mOpenCount > 1) return 0; // already open
109
110 assert(!folder()->name().isEmpty());
111
112 rc = canAccess();
113 if ( rc != 0 ) {
114 return rc;
115 }
116
117 if (!folder()->path().isEmpty())
118 {
119 if (KMFolderIndex::IndexOk != indexStatus()) // test if contents file has changed
120 {
121 TQString str;
122 mIndexStream = 0;
123 str = i18n("Folder `%1' changed; recreating index.")
124 .arg(name());
125 emit statusMsg(str);
126 } else {
127 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "r+"); // index file
128 if ( mIndexStream ) {
129 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
130 updateIndexStreamPtr();
131 }
132 }
133
134 if (!mIndexStream)
136 else
137 readIndex();
138 }
139 else
140 {
141 mAutoCreateIndex = false;
143 }
144
145 mChanged = false;
146
147 //readConfig();
148
149 return rc;
150}
151
152
153//-----------------------------------------------------------------------------
154int KMFolderMaildir::createMaildirFolders( const TQString & folderPath )
155{
156 // Make sure that neither a new, cur or tmp subfolder exists already.
157 TQFileInfo dirinfo;
158 dirinfo.setFile( folderPath + "/new" );
159 if ( dirinfo.exists() ) return EEXIST;
160 dirinfo.setFile( folderPath + "/cur" );
161 if ( dirinfo.exists() ) return EEXIST;
162 dirinfo.setFile( folderPath + "/tmp" );
163 if ( dirinfo.exists() ) return EEXIST;
164
165 // create the maildir directory structure
166 if ( ::mkdir( TQFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
167 kdDebug(5006) << "Could not create folder " << folderPath << endl;
168 return errno;
169 }
170 if ( ::mkdir( TQFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
171 kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl;
172 return errno;
173 }
174 if ( ::mkdir( TQFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
175 kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl;
176 return errno;
177 }
178 if ( ::mkdir( TQFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
179 kdDebug(5006) << "Could not create folder " << folderPath << "/tmp" << endl;
180 return errno;
181 }
182
183 return 0; // no error
184}
185
186//-----------------------------------------------------------------------------
188{
189 int rc;
190 int old_umask;
191
192 assert(!folder()->name().isEmpty());
193 assert(mOpenCount == 0);
194
195 rc = createMaildirFolders( location() );
196 if ( rc != 0 )
197 return rc;
198
199 // FIXME no path == no index? - till
200 if (!folder()->path().isEmpty())
201 {
202 old_umask = umask(077);
203 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "w+"); //sven; open RW
204 updateIndexStreamPtr(true);
205 umask(old_umask);
206
207 if (!mIndexStream) return errno;
208 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
209 }
210 else
211 {
212 mAutoCreateIndex = false;
213 }
214
215 mOpenCount++;
216 mChanged = false;
217
218 rc = writeIndex();
219 return rc;
220}
221
222
223//-----------------------------------------------------------------------------
224void KMFolderMaildir::reallyDoClose(const char* owner)
225{
226 Q_UNUSED( owner );
228 {
229 updateIndex();
230 writeConfig();
231 }
232
233 mMsgList.clear(true);
234
235 if (mIndexStream) {
236 fclose(mIndexStream);
237 updateIndexStreamPtr(true);
238 }
239
240 mOpenCount = 0;
241 mIndexStream = 0;
242 mUnreadMsgs = -1;
243
244 mMsgList.reset(INIT_MSGS);
245}
246
247//-----------------------------------------------------------------------------
249{
250 if (mOpenCount > 0)
251 if (!mIndexStream || fsync(fileno(mIndexStream))) {
252 kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
253 }
254}
255
256//-----------------------------------------------------------------------------
258{
259 // nuke all messages in this folder now
260 TQDir d(location() + "/new");
261 // d.setFilter(TQDir::Files); coolo: TQFile::remove returns false for non-files
262 TQStringList files(d.entryList());
263 TQStringList::ConstIterator it(files.begin());
264 for ( ; it != files.end(); ++it)
265 TQFile::remove(d.filePath(*it));
266
267 d.setPath(location() + "/cur");
268 files = d.entryList();
269 for (it = files.begin(); it != files.end(); ++it)
270 TQFile::remove(d.filePath(*it));
271
272 return 0;
273}
274
275int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const TQStringList& entryList, bool& done )
276{
277 TQString subdirNew(location() + "/new/");
278 TQString subdirCur(location() + "/cur/");
279
280 unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
281 TQMIN( mMsgList.count(), startIndex + nbMessages );
282 //kdDebug(5006) << "KMFolderMaildir: compacting from " << startIndex << " to " << stopIndex << endl;
283 for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
284 KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
285 if (!mi)
286 continue;
287
288 TQString filename(mi->fileName());
289 if (filename.isEmpty())
290 continue;
291
292 // first, make sure this isn't in the 'new' subdir
293 if ( entryList.contains( filename ) )
294 moveInternal(subdirNew + filename, subdirCur + filename, mi);
295
296 // construct a valid filename. if it's already valid, then
297 // nothing happens
298 filename = constructValidFileName( filename, mi->status() );
299
300 // if the name changed, then we need to update the actual filename
301 if (filename != mi->fileName())
302 {
303 moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi);
304 mi->setFileName(filename);
305 setDirty( true );
306 }
307
308#if 0
309 // we can't have any New messages at this point
310 if (mi->isNew())
311 {
312 mi->setStatus(KMMsgStatusUnread);
313 setDirty( true );
314 }
315#endif
316 }
317 done = ( stopIndex == mMsgList.count() );
318 return 0;
319}
320
321//-----------------------------------------------------------------------------
322int KMFolderMaildir::compact( bool silent )
323{
324 KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ );
325 int rc = job->executeNow( silent );
326 // Note that job autodeletes itself.
327 return rc;
328}
329
330//-------------------------------------------------------------
331FolderJob*
332KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
333 KMFolder *folder, TQString, const AttachmentStrategy* ) const
334{
335 MaildirJob *job = new MaildirJob( msg, jt, folder );
336 job->setParentFolder( this );
337 return job;
338}
339
340//-------------------------------------------------------------
341FolderJob*
342KMFolderMaildir::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets,
343 FolderJob::JobType jt, KMFolder *folder ) const
344{
345 MaildirJob *job = new MaildirJob( msgList, sets, jt, folder );
346 job->setParentFolder( this );
347 return job;
348}
349
350//-------------------------------------------------------------
351int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return)
352{
353 if (!canAddMsgNow(aMsg, index_return)) return 0;
354 return addMsgInternal( aMsg, index_return );
355}
356
357//-------------------------------------------------------------
358int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
359 bool stripUid )
360{
361/*
362TQFile fileD0( "testdat_xx-kmfoldermaildir-0" );
363if( fileD0.open( IO_WriteOnly ) ) {
364 TQDataStream ds( &fileD0 );
365 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
366 fileD0.close(); // If data is 0 we just create a zero length file.
367}
368*/
369 long len;
370 unsigned long size;
371 KMFolder* msgParent;
372 TQCString msgText;
373 int idx(-1);
374 int rc;
375
376 // take message out of the folder it is currently in, if any
377 msgParent = aMsg->parent();
378 if (msgParent)
379 {
380 if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder()))
381 return 0;
382
383 idx = msgParent->find(aMsg);
384 msgParent->getMsg( idx );
385 }
386
387 aMsg->setStatusFields();
388 if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by
389 aMsg->removeHeaderField("Content-Type"); // the line above
390
391
392 const TQString uidHeader = aMsg->headerField( "X-UID" );
393 if ( !uidHeader.isEmpty() && stripUid )
394 aMsg->removeHeaderField( "X-UID" );
395
396 msgText = aMsg->asString(); // TODO use asDwString instead
397 len = msgText.length();
398
399 // Re-add the uid so that the take can make use of it, in case the
400 // message is currently in an imap folder
401 if ( !uidHeader.isEmpty() && stripUid )
402 aMsg->setHeaderField( "X-UID", uidHeader );
403
404 if (len <= 0)
405 {
406 kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
407 return 0;
408 }
409
410 // make sure the filename has the correct extension
411 TQString filename = constructValidFileName( aMsg->fileName(), aMsg->status() );
412
413 TQString tmp_file(location() + "/tmp/");
414 tmp_file += filename;
415
416 if (!KPIM::kCStringToFile(msgText, tmp_file, false, false, false))
417 kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") );
418
419 TQFile file(tmp_file);
420 size = msgText.length();
421
422 KMFolderOpener openThis(folder(), "maildir");
423 rc = openThis.openResult();
424 if (rc)
425 {
426 kdDebug(5006) << "KMFolderMaildir::addMsg-open: " << rc << " of folder: " << label() << endl;
427 return rc;
428 }
429
430 // now move the file to the correct location
431 TQString new_loc(location() + "/cur/");
432 new_loc += filename;
433 if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull())
434 {
435 file.remove();
436 return -1;
437 }
438
439 if (msgParent && idx >= 0)
440 msgParent->take(idx);
441
442 // just to be sure it does not end up in the index
443 if ( stripUid ) aMsg->setUID( 0 );
444
445 if (filename != aMsg->fileName())
446 aMsg->setFileName(filename);
447
448 if (aMsg->isUnread() || aMsg->isNew() || folder() == kmkernel->outboxFolder())
449 {
450 if (mUnreadMsgs == -1)
451 mUnreadMsgs = 1;
452 else
453 ++mUnreadMsgs;
454 if ( !mQuiet ) {
455 kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl;
456 emit numUnreadMsgsChanged( folder() );
457 }else{
458 if ( !mEmitChangedTimer->isActive() ) {
459// kdDebug( 5006 )<< "QuietTimer started" << endl;
460 mEmitChangedTimer->start( 3000 );
461 }
462 mChanged = true;
463 }
464 }
465 ++mTotalMsgs;
466 mSize = -1;
467
468 if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) {
469 aMsg->updateAttachmentState();
470 }
471 if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
472 aMsg->updateInvitationState();
473 }
474
475 // store information about the position in the folder file in the message
476 aMsg->setParent(folder());
477 aMsg->setMsgSize(size);
478 idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
479 if (aMsg->getMsgSerNum() <= 0)
480 aMsg->setMsgSerNum();
481 else
482 replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
483
484 // write index entry if desired
486 {
487 assert(mIndexStream != 0);
488 clearerr(mIndexStream);
489 fseek(mIndexStream, 0, SEEK_END);
490 off_t revert = ftell(mIndexStream);
491
492 int len;
493 KMMsgBase * mb = &aMsg->toMsgBase();
494 const uchar *buffer = mb->asIndexString(len);
495 fwrite(&len,sizeof(len), 1, mIndexStream);
496 mb->setIndexOffset( ftell(mIndexStream) );
497 mb->setIndexLength( len );
498 if(fwrite(buffer, len, 1, mIndexStream) != 1)
499 kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
500
501 fflush(mIndexStream);
502 int error = ferror(mIndexStream);
503
504 if ( mExportsSernums )
505 error |= appendToFolderIdsFile( idx );
506
507 if (error) {
508 kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
509 if (ftell(mIndexStream) > revert) {
510 kdDebug(5006) << "Undoing changes" << endl;
511 truncate( TQFile::encodeName(indexLocation()), revert );
512 }
513 kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss."));
514 // exit(1); // don't ever use exit(), use the above!
515
516 /* This code may not be 100% reliable
517 bool busy = kmkernel->kbp()->isBusy();
518 if (busy) kmkernel->kbp()->idle();
519 KMessageBox::sorry(0,
520 i18n("Unable to add message to folder.\n"
521 "(No space left on device or insufficient quota?)\n"
522 "Free space and sufficient quota are required to continue safely."));
523 if (busy) kmkernel->kbp()->busy();
524 */
525 return error;
526 }
527 }
528
529 if (index_return)
530 *index_return = idx;
531
533 needsCompact = true;
534
535/*
536TQFile fileD1( "testdat_xx-kmfoldermaildir-1" );
537if( fileD1.open( IO_WriteOnly ) ) {
538 TQDataStream ds( &fileD1 );
539 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
540 fileD1.close(); // If data is 0 we just create a zero length file.
541}
542*/
543 return 0;
544}
545
546KMMessage* KMFolderMaildir::readMsg(int idx)
547{
548 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
549 KMMessage *msg = new KMMessage(*mi);
550 msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
551 mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
552 msg->setComplete( true );
553 msg->fromDwString(getDwString(idx));
554 return msg;
555}
556
557DwString KMFolderMaildir::getDwString(int idx)
558{
559 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
560 TQString abs_file(location() + "/cur/");
561 abs_file += mi->fileName();
562 TQFileInfo fi( abs_file );
563
564 if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
565 {
566 FILE* stream = fopen(TQFile::encodeName(abs_file), "r+");
567 if (stream) {
568 size_t msgSize = fi.size();
569 char* msgText = new char[ msgSize + 1 ];
570 fread(msgText, msgSize, 1, stream);
571 fclose( stream );
572 msgText[msgSize] = '\0';
573 size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
574 DwString str;
575 // the DwString takes possession of msgText, so we must not delete it
576 str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
577 return str;
578 }
579 }
580 kdDebug(5006) << "Could not open file r+ " << abs_file << endl;
581 return DwString();
582}
583
584
585void KMFolderMaildir::readFileHeaderIntern(const TQString& dir, const TQString& file, KMMsgStatus status)
586{
587 // we keep our current directory to restore it later
588 char path_buffer[PATH_MAX];
589 if(!::getcwd(path_buffer, PATH_MAX - 1))
590 return;
591
592 ::chdir(TQFile::encodeName(dir));
593
594 // messages in the 'cur' directory are Read by default.. but may
595 // actually be some other state (but not New)
596 if (status == KMMsgStatusRead)
597 {
598 if (file.find(":2,") == -1)
599 status = KMMsgStatusUnread;
600 else if (file.right(5) == ":2,RS")
601 status |= KMMsgStatusReplied;
602 }
603
604 // open the file and get a pointer to it
605 TQFile f(file);
606 if ( f.open( IO_ReadOnly ) == false ) {
607 kdWarning(5006) << "The file '" << TQString(TQFile::encodeName(dir)) << "/" << file
608 << "' could not be opened for reading the message. "
609 "Please check ownership and permissions."
610 << endl;
611 return;
612 }
613
614 char line[MAX_LINE];
615 bool atEof = false;
616 bool inHeader = true;
617 TQCString *lastStr = 0;
618
619 TQCString dateStr, fromStr, toStr, subjStr;
620 TQCString xmarkStr, replyToIdStr, msgIdStr, referencesStr;
621 TQCString statusStr, replyToAuxIdStr, uidStr;
622 TQCString contentTypeStr, charset;
623
624 // iterate through this file until done
625 while (!atEof)
626 {
627 // if the end of the file has been reached or if there was an error
628 if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) )
629 atEof = true;
630
631 // are we done with this file? if so, compile our info and store
632 // it in a KMMsgInfo object
633 if (atEof || !inHeader)
634 {
635 msgIdStr = msgIdStr.stripWhiteSpace();
636 if( !msgIdStr.isEmpty() ) {
637 int rightAngle;
638 rightAngle = msgIdStr.find( '>' );
639 if( rightAngle != -1 )
640 msgIdStr.truncate( rightAngle + 1 );
641 }
642
643 replyToIdStr = replyToIdStr.stripWhiteSpace();
644 if( !replyToIdStr.isEmpty() ) {
645 int rightAngle;
646 rightAngle = replyToIdStr.find( '>' );
647 if( rightAngle != -1 )
648 replyToIdStr.truncate( rightAngle + 1 );
649 }
650
651 referencesStr = referencesStr.stripWhiteSpace();
652 if( !referencesStr.isEmpty() ) {
653 int leftAngle, rightAngle;
654 leftAngle = referencesStr.findRev( '<' );
655 if( ( leftAngle != -1 )
656 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
657 // use the last reference, instead of missing In-Reply-To
658 replyToIdStr = referencesStr.mid( leftAngle );
659 }
660
661 // find second last reference
662 leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
663 if( leftAngle != -1 )
664 referencesStr = referencesStr.mid( leftAngle );
665 rightAngle = referencesStr.findRev( '>' );
666 if( rightAngle != -1 )
667 referencesStr.truncate( rightAngle + 1 );
668
669 // Store the second to last reference in the replyToAuxIdStr
670 // It is a good candidate for threading the message below if the
671 // message In-Reply-To points to is not kept in this folder,
672 // but e.g. in an Outbox
673 replyToAuxIdStr = referencesStr;
674 rightAngle = referencesStr.find( '>' );
675 if( rightAngle != -1 )
676 replyToAuxIdStr.truncate( rightAngle + 1 );
677 }
678
679 statusStr = statusStr.stripWhiteSpace();
680 if (!statusStr.isEmpty())
681 {
682 // only handle those states not determined by the file suffix
683 if (statusStr[0] == 'S')
684 status |= KMMsgStatusSent;
685 else if (statusStr[0] == 'F')
686 status |= KMMsgStatusForwarded;
687 else if (statusStr[0] == 'D')
688 status |= KMMsgStatusDeleted;
689 else if (statusStr[0] == 'Q')
690 status |= KMMsgStatusQueued;
691 else if (statusStr[0] == 'G')
692 status |= KMMsgStatusFlag;
693 }
694
695 contentTypeStr = contentTypeStr.stripWhiteSpace();
696 charset = "";
697 if ( !contentTypeStr.isEmpty() )
698 {
699 int cidx = contentTypeStr.find( "charset=" );
700 if ( cidx != -1 ) {
701 charset = contentTypeStr.mid( cidx + 8 );
702 if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
703 charset = charset.mid( 1 );
704 }
705 cidx = 0;
706 while ( (unsigned int) cidx < charset.length() ) {
707 if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
708 charset[cidx] != '-' && charset[cidx] != '_' ) )
709 break;
710 ++cidx;
711 }
712 charset.truncate( cidx );
713 // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
714 // charset << " from " << contentTypeStr << endl;
715 }
716 }
717
718 KMMsgInfo *mi = new KMMsgInfo(folder());
719 mi->init( subjStr.stripWhiteSpace(),
720 fromStr.stripWhiteSpace(),
721 toStr.stripWhiteSpace(),
722 0, status,
723 xmarkStr.stripWhiteSpace(),
724 replyToIdStr, replyToAuxIdStr, msgIdStr,
725 file.local8Bit(),
726 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
727 KMMsgMDNStateUnknown, charset, f.size() );
728
729 dateStr = dateStr.stripWhiteSpace();
730 if (!dateStr.isEmpty())
731 mi->setDate(dateStr.data());
732 if ( !uidStr.isEmpty() )
733 mi->setUID( uidStr.toULong() );
734 mi->setDirty(false);
735 mMsgList.append( mi, mExportsSernums );
736
737 // if this is a New file and is in 'new', we move it to 'cur'
738 if (status & KMMsgStatusNew)
739 {
740 TQString newDir(location() + "/new/");
741 TQString curDir(location() + "/cur/");
742 moveInternal(newDir + file, curDir + file, mi);
743 }
744
745 break;
746 }
747
748 // Is this a long header line?
749 if (inHeader && ( line[0] == '\t' || line[0] == ' ' ) )
750 {
751 int i = 0;
752 while (line[i] == '\t' || line[i] == ' ')
753 i++;
754 if (line[i] < ' ' && line[i] > 0)
755 inHeader = false;
756 else
757 if (lastStr)
758 *lastStr += line + i;
759 }
760 else
761 lastStr = 0;
762
763 if (inHeader && (line[0] == '\n' || line[0] == '\r'))
764 inHeader = false;
765 if (!inHeader)
766 continue;
767
768 if (strncasecmp(line, "Date:", 5) == 0)
769 {
770 dateStr = TQCString(line+5);
771 lastStr = &dateStr;
772 }
773 else if (strncasecmp(line, "From:", 5) == 0)
774 {
775 fromStr = TQCString(line+5);
776 lastStr = &fromStr;
777 }
778 else if (strncasecmp(line, "To:", 3) == 0)
779 {
780 toStr = TQCString(line+3);
781 lastStr = &toStr;
782 }
783 else if (strncasecmp(line, "Subject:", 8) == 0)
784 {
785 subjStr = TQCString(line+8);
786 lastStr = &subjStr;
787 }
788 else if (strncasecmp(line, "References:", 11) == 0)
789 {
790 referencesStr = TQCString(line+11);
791 lastStr = &referencesStr;
792 }
793 else if (strncasecmp(line, "Message-Id:", 11) == 0)
794 {
795 msgIdStr = TQCString(line+11);
796 lastStr = &msgIdStr;
797 }
798 else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0)
799 {
800 xmarkStr = TQCString(line+13);
801 }
802 else if (strncasecmp(line, "X-Status:", 9) == 0)
803 {
804 statusStr = TQCString(line+9);
805 }
806 else if (strncasecmp(line, "In-Reply-To:", 12) == 0)
807 {
808 replyToIdStr = TQCString(line+12);
809 lastStr = &replyToIdStr;
810 }
811 else if (strncasecmp(line, "X-UID:", 6) == 0)
812 {
813 uidStr = TQCString(line+6);
814 lastStr = &uidStr;
815 }
816 else if (strncasecmp(line, "Content-Type:", 13) == 0)
817 {
818 contentTypeStr = TQCString(line+13);
819 lastStr = &contentTypeStr;
820 }
821
822 }
823
824 if (status & KMMsgStatusNew || status & KMMsgStatusUnread ||
825 (folder() == kmkernel->outboxFolder()))
826 {
827 mUnreadMsgs++;
828 if (mUnreadMsgs == 0) ++mUnreadMsgs;
829 }
830
831 ::chdir(path_buffer);
832}
833
835{
836 mUnreadMsgs = 0;
837
838 mMsgList.clear(true);
839 mMsgList.reset(INIT_MSGS);
840
841 mChanged = false;
842
843 // first, we make sure that all the directories are here as they
844 // should be
845 TQFileInfo dirinfo;
846
847 dirinfo.setFile(location() + "/new");
848 if (!dirinfo.exists() || !dirinfo.isDir())
849 {
850 kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl;
851 return 1;
852 }
853 TQDir newDir(location() + "/new");
854 newDir.setFilter(TQDir::Files);
855
856 dirinfo.setFile(location() + "/cur");
857 if (!dirinfo.exists() || !dirinfo.isDir())
858 {
859 kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl;
860 return 1;
861 }
862 TQDir curDir(location() + "/cur");
863 curDir.setFilter(TQDir::Files);
864
865 // then, we look for all the 'cur' files
866 const TQFileInfoList *list = curDir.entryInfoList();
867 TQFileInfoListIterator it(*list);
868 TQFileInfo *fi;
869
870 while ((fi = it.current()))
871 {
872 readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead);
873 ++it;
874 }
875
876 // then, we look for all the 'new' files
877 list = newDir.entryInfoList();
878 it = *list;
879
880 while ((fi=it.current()))
881 {
882 readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew);
883 ++it;
884 }
885
886 if ( autoCreateIndex() ) {
887 emit statusMsg(i18n("Writing index file"));
888 writeIndex();
889 }
890 else mHeaderOffset = 0;
891
893
894 if (kmkernel->outboxFolder() == folder() && count() > 0)
895 KMessageBox::information(0, i18n("Your outbox contains messages which were "
896 "most-likely not created by KMail;\nplease remove them from there if you "
897 "do not want KMail to send them."));
898
899 needsCompact = true;
900
902 return 0;
903}
904
906{
907 if ( !mCompactable )
908 return KMFolderIndex::IndexCorrupt;
909
910 TQFileInfo new_info(location() + "/new");
911 TQFileInfo cur_info(location() + "/cur");
912 TQFileInfo index_info(indexLocation());
913
914 if (!index_info.exists())
915 return KMFolderIndex::IndexMissing;
916
917 // Check whether the directories are more than 5 seconds newer than the index
918 // file. The 5 seconds are added to reduce the number of false alerts due
919 // to slightly out of sync clocks of the NFS server and the local machine.
920 return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) ||
921 (cur_info.lastModified() > index_info.lastModified().addSecs(5)))
922 ? KMFolderIndex::IndexTooOld
923 : KMFolderIndex::IndexOk;
924}
925
926//-----------------------------------------------------------------------------
927void KMFolderMaildir::removeMsg(int idx, bool)
928{
929 KMMsgBase* msg = mMsgList[idx];
930 if (!msg || !msg->fileName()) return;
931
932 removeFile(msg->fileName());
933
935}
936
937//-----------------------------------------------------------------------------
938KMMessage* KMFolderMaildir::take(int idx)
939{
940 // first, we do the high-level stuff.. then delete later
941 KMMessage *msg = KMFolderIndex::take(idx);
942
943 if (!msg || !msg->fileName()) {
944 return 0;
945 }
946
947 if ( removeFile(msg->fileName()) ) {
948 return msg;
949 } else {
950 return 0;
951 }
952}
953
954// static
955bool KMFolderMaildir::removeFile( const TQString & folderPath,
956 const TQString & filename )
957{
958 // we need to look in both 'new' and 'cur' since it's possible to
959 // delete a message before the folder is compacted. Since the file
960 // naming and moving is done in ::compact, we can't assume any
961 // location at this point.
962 TQCString abs_file( TQFile::encodeName( folderPath + "/cur/" + filename ) );
963 if ( ::unlink( abs_file ) == 0 )
964 return true;
965
966 if ( errno == ENOENT ) { // doesn't exist
967 abs_file = TQFile::encodeName( folderPath + "/new/" + filename );
968 if ( ::unlink( abs_file ) == 0 )
969 return true;
970 }
971
972 kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl;
973 return false;
974}
975
976bool KMFolderMaildir::removeFile( const TQString & filename )
977{
978 return removeFile( location(), filename );
979}
980
981#include <sys/types.h>
982#include <dirent.h>
983static bool removeDirAndContentsRecursively( const TQString & path )
984{
985 bool success = true;
986
987 TQDir d;
988 d.setPath( path );
989 d.setFilter( TQDir::Files | TQDir::Dirs | TQDir::Hidden | TQDir::NoSymLinks );
990
991 const TQFileInfoList *list = d.entryInfoList();
992 TQFileInfoListIterator it( *list );
993 TQFileInfo *fi;
994
995 while ( (fi = it.current()) != 0 ) {
996 if( fi->isDir() ) {
997 if ( fi->fileName() != "." && fi->fileName() != ".." )
998 success = success && removeDirAndContentsRecursively( fi->absFilePath() );
999 } else {
1000 success = success && d.remove( fi->absFilePath() );
1001 }
1002 ++it;
1003 }
1004
1005 if ( success ) {
1006 success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
1007 }
1008 return success;
1009}
1010
1011//-----------------------------------------------------------------------------
1013{
1014 // NOTE: Don' use TDEIO::netaccess, it has reentrancy problems and multiple
1015 // mailchecks going on trigger them, when removing dirs
1016 if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1;
1017 if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1;
1018 if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1;
1019 /* The subdirs are removed now. Check if there is anything else in the dir
1020 * and only if not delete the dir itself. The user could have data stored
1021 * that would otherwise be deleted. */
1022 TQDir dir(location());
1023 if ( dir.count() == 2 ) { // only . and ..
1024 if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1;
1025 }
1026 return 0;
1027}
1028
1029static TQRegExp *suffix_regex = 0;
1030static KStaticDeleter<TQRegExp> suffix_regex_sd;
1031
1032//-----------------------------------------------------------------------------
1033// static
1034TQString KMFolderMaildir::constructValidFileName( const TQString & filename,
1035 KMMsgStatus status )
1036{
1037 TQString aFileName( filename );
1038
1039 if (aFileName.isEmpty())
1040 {
1041 aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
1042 aFileName += TDEApplication::randomString(5);
1043 }
1044
1045 if (!suffix_regex)
1046 suffix_regex_sd.setObject(suffix_regex, new TQRegExp(":2,?R?S?$"));
1047
1048 aFileName.truncate(aFileName.findRev(*suffix_regex));
1049
1050 // only add status suffix if the message is neither new nor unread
1051 if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) )
1052 {
1053 TQString suffix( ":2," );
1054 if (status & KMMsgStatusReplied)
1055 suffix += "RS";
1056 else
1057 suffix += "S";
1058 aFileName += suffix;
1059 }
1060
1061 return aFileName;
1062}
1063
1064//-----------------------------------------------------------------------------
1065TQString KMFolderMaildir::moveInternal(const TQString& oldLoc, const TQString& newLoc, KMMsgInfo *mi)
1066{
1067 TQString filename(mi->fileName());
1068 TQString ret(moveInternal(oldLoc, newLoc, filename, mi->status()));
1069
1070 if (filename != mi->fileName())
1071 mi->setFileName(filename);
1072
1073 return ret;
1074}
1075
1076//-----------------------------------------------------------------------------
1077TQString KMFolderMaildir::moveInternal(const TQString& oldLoc, const TQString& newLoc, TQString& aFileName, KMMsgStatus status)
1078{
1079 TQString dest(newLoc);
1080 // make sure that our destination filename doesn't already exist
1081 while (TQFile::exists(dest))
1082 {
1083 aFileName = constructValidFileName( TQString(), status );
1084
1085 TQFileInfo fi(dest);
1086 dest = fi.dirPath(true) + "/" + aFileName;
1087 setDirty( true );
1088 }
1089
1090 TQDir d;
1091 if (d.rename(oldLoc, dest) == false)
1092 return TQString();
1093 else
1094 return dest;
1095}
1096
1097//-----------------------------------------------------------------------------
1098void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus,
1099 const KMMsgStatus newStatus, int idx)
1100{
1101 // if the status of any message changes, then we need to compact
1102 needsCompact = true;
1103
1104 KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
1105}
1106
1107/*virtual*/
1108TQ_INT64 KMFolderMaildir::doFolderSize() const
1109{
1110 if ( mCurrentlyCheckingFolderSize )
1111 {
1112 return -1;
1113 }
1114 mCurrentlyCheckingFolderSize = true;
1115
1116 KFileItemList list;
1117 KFileItem *item = 0;
1118 item = new KFileItem( S_IFDIR, -1, location() + "/cur" );
1119 list.append( item );
1120 item = new KFileItem( S_IFDIR, -1, location() + "/new" );
1121 list.append( item );
1122 item = new KFileItem( S_IFDIR, -1, location() + "/tmp" );
1123 list.append( item );
1124 s_DirSizeJobQueue.append(
1125 qMakePair( TQGuardedPtr<const KMFolderMaildir>( this ), list ) );
1126
1127 // if there's only one entry in the queue then we can start
1128 // a dirSizeJob right away
1129 if ( s_DirSizeJobQueue.size() == 1 )
1130 {
1131 //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
1132 // << location() << endl;
1133 KDirSize* job = KDirSize::dirSizeJob( list );
1134 connect( job, TQ_SIGNAL( result( TDEIO::Job* ) ),
1135 this, TQ_SLOT( slotDirSizeJobResult( TDEIO::Job* ) ) );
1136 }
1137
1138 return -1;
1139}
1140
1141void KMFolderMaildir::slotDirSizeJobResult( TDEIO::Job* job )
1142{
1143 mCurrentlyCheckingFolderSize = false;
1144 KDirSize * dirsize = dynamic_cast<KDirSize*>( job );
1145 if ( dirsize && ! dirsize->error() )
1146 {
1147 mSize = dirsize->totalSize();
1148 //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder "
1149 // << location() << " has size " << mSize << endl;
1150 emit folderSizeChanged();
1151 }
1152 // remove the completed job from the queue
1153 s_DirSizeJobQueue.pop_front();
1154
1155 // process the next entry in the queue
1156 while ( s_DirSizeJobQueue.size() > 0 )
1157 {
1158 DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first();
1159 // check whether the entry is valid, i.e. whether the folder still exists
1160 if ( entry.first )
1161 {
1162 // start the next dirSizeJob
1163 //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
1164 // << entry.first->location() << endl;
1165 KDirSize* job = KDirSize::dirSizeJob( entry.second );
1166 connect( job, TQ_SIGNAL( result( TDEIO::Job* ) ),
1167 entry.first, TQ_SLOT( slotDirSizeJobResult( TDEIO::Job* ) ) );
1168 break;
1169 }
1170 else
1171 {
1172 // remove the invalid entry from the queue
1173 s_DirSizeJobQueue.pop_front();
1174 }
1175 }
1176}
1177
1178#include "kmfoldermaildir.moc"
void folderSizeChanged()
Emitted when the folder's size changes.
virtual int create()=0
Create a new folder with the name of this object and open it.
virtual KMMessage * take(int idx)
Detach message from this folder.
void emitMsgAddedSignals(int idx)
Called by derived classes implementation of addMsg.
virtual void msgStatusChanged(const KMMsgStatus oldStatus, const KMMsgStatus newStatus, int idx)
Called by KMMsgBase::setStatus when status of a message has changed required to keep the number unrea...
int appendToFolderIdsFile(int idx=-1)
Append message to end of message serial number file.
bool needsCompact
sven: true if on destruct folder needs to be compacted.
bool autoCreateIndex() const
Returns true if a table of contents file is automatically created.
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 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 int expungeContents()=0
Called by KMFolder::expunge() to delete the actual contents.
virtual void removeMsg(int i, bool imapQuiet=false)
Remove (first occurrence of) given message from the folder.
virtual bool canAddMsgNow(KMMessage *aMsg, int *aIndex_ret)
Returns false, if the message has to be retrieved from an IMAP account first.
void setDirty(bool f)
Change the dirty flag.
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.
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.
TQCString asString() const
Return the entire message contents as a string.
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition kmmessage.h:114
void setMsgSerNum(unsigned long newMsgSerNum=0)
Sets the message serial number.
void setComplete(bool v)
Set if the message is a complete message.
Definition kmmessage.h:869
KMMsgStatus status() const
Status of the message.
Definition kmmessage.h:830
TQString headerField(const TQCString &name) const
Returns the value of a header field with the given name.
TQString fileName() const
Get/set filename in mail folder.
Definition kmmessage.h:802
void setMsgInfo(KMMsgInfo *msgInfo)
Set the KMMsgInfo object corresponding to this message.
Definition kmmessage.h:932
void setHeaderField(const TQCString &name, const TQString &value, HeaderFieldType type=Unstructured, bool prepend=false)
Set the header field with the given name to the given value.
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