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"
19 using 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
50 TQValueList<KMFolderMaildir::DirSizeJobQueueEntry> KMFolderMaildir::s_DirSizeJobQueue;
51 
52 //-----------------------------------------------------------------------------
53 KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name)
54  : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false)
55 {
56 
57 }
58 
59 
60 //-----------------------------------------------------------------------------
61 KMFolderMaildir::~KMFolderMaildir()
62 {
63  if (mOpenCount>0) close("~foldermaildir", true);
64  if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
65 }
66 
67 //-----------------------------------------------------------------------------
68 int 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 //-----------------------------------------------------------------------------
101 int 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 //-----------------------------------------------------------------------------
154 int 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 //-----------------------------------------------------------------------------
187 int 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 //-----------------------------------------------------------------------------
224 void 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 //-----------------------------------------------------------------------------
248 void 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 //-----------------------------------------------------------------------------
257 int 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 
275 int 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 //-----------------------------------------------------------------------------
322 int 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 //-------------------------------------------------------------
331 FolderJob*
332 KMFolderMaildir::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 //-------------------------------------------------------------
341 FolderJob*
342 KMFolderMaildir::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 //-------------------------------------------------------------
351 int 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 //-------------------------------------------------------------
358 int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
359  bool stripUid )
360 {
361 /*
362 TQFile fileD0( "testdat_xx-kmfoldermaildir-0" );
363 if( 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 /*
536 TQFile fileD1( "testdat_xx-kmfoldermaildir-1" );
537 if( 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 
546 KMMessage* 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 
557 DwString 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 
585 void 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 
834 int 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 
905 KMFolderIndex::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 //-----------------------------------------------------------------------------
927 void 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 //-----------------------------------------------------------------------------
938 KMMessage* 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
955 bool 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 
976 bool KMFolderMaildir::removeFile( const TQString & filename )
977 {
978  return removeFile( location(), filename );
979 }
980 
981 #include <sys/types.h>
982 #include <dirent.h>
983 static 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 //-----------------------------------------------------------------------------
1012 int 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 
1029 static TQRegExp *suffix_regex = 0;
1030 static KStaticDeleter<TQRegExp> suffix_regex_sd;
1031 
1032 //-----------------------------------------------------------------------------
1033 // static
1034 TQString 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 //-----------------------------------------------------------------------------
1065 TQString 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 //-----------------------------------------------------------------------------
1077 TQString 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 //-----------------------------------------------------------------------------
1098 void 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*/
1108 TQ_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 
1141 void 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
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
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
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