kmail

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