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//-----------------------------------------------------------------------------
68int KMFolderMaildir::canAccess()
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)
135 rc = createIndexFromContents();
136 else
137 readIndex();
138 }
139 else
140 {
141 mAutoCreateIndex = false;
142 rc = createIndexFromContents();
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//-----------------------------------------------------------------------------
187int KMFolderMaildir::create()
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 );
227 if (mAutoCreateIndex)
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//-----------------------------------------------------------------------------
248void KMFolderMaildir::sync()
249{
250 if (mOpenCount > 0)
251 if (!mIndexStream || fsync(fileno(mIndexStream))) {
252 kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
253 }
254}
255
256//-----------------------------------------------------------------------------
257int KMFolderMaildir::expungeContents()
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
485 if (mAutoCreateIndex)
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
532 emitMsgAddedSignals(idx);
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
834int KMFolderMaildir::createIndexFromContents()
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
892 correctUnreadMsgsCount();
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
901 invalidateFolder();
902 return 0;
903}
904
905KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
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//-----------------------------------------------------------------------------
1012int KMFolderMaildir::removeContents()
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"
virtual KMMessage * take(int idx)
Detach message from this folder.
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...
virtual void removeMsg(int i, bool imapQuiet=false)
Remove (first occurrence of) given message from the folder.
virtual int addMsg(TQPtrList< KMMessage > &, TQValueList< int > &index_return)
Adds the given messages to the folder.
sets a cursor and makes sure it's restored on destruction Create a KCursorSaver object when you want ...
Definition: kcursorsaver.h:14
A FolderStorage with an index for faster access to often used message properties.
Definition: kmfolderindex.h:38
IndexStatus
This enum indicates the status of the index file.
Definition: kmfolderindex.h:50
RAII for KMFolder::open() / close().
Definition: kmfolder.h:688
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.
Definition: kmmessage.cpp:351
void removeHeaderField(const TQCString &name)
Remove header field with given name.
Definition: kmmessage.cpp:2317
void fromDwString(const DwString &str, bool setStatus=false)
Parse the string and create this message from it.
Definition: kmmessage.cpp:402
TQCString asString() const
Return the entire message contents as a string.
Definition: kmmessage.cpp:314
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
void setMsgSerNum(unsigned long newMsgSerNum=0)
Sets the message serial number.
Definition: kmmessage.cpp:223
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.
Definition: kmmessage.cpp:2289
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.
Definition: kmmessage.cpp:2339
A job that runs in the background and compacts maildir folders.
Definition: compactionjob.h:74
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