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)
136  else
137  readIndex();
138  }
139  else
140  {
141  mAutoCreateIndex = false;
143  }
144 
145  mChanged = false;
146 
147  //readConfig();
148 
149  return rc;
150 }
151 
152 
153 //-----------------------------------------------------------------------------
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);
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 
893 
894  if (kmkernel->outboxFolder() == folder() && count() > 0)
895  KMessageBox::information(0, i18n("Your outbox contains messages which were "
896  "most-likely not created by KMail;\nplease remove them from there if you "
897  "do not want KMail to send them."));
898 
899  needsCompact = true;
900 
902  return 0;
903 }
904 
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  tqMakePair( 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, TQT_SIGNAL( result( TDEIO::Job* ) ),
1135  this, TQT_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, TQT_SIGNAL( result( TDEIO::Job* ) ),
1167  entry.first, TQT_SLOT( slotDirSizeJobResult( TDEIO::Job* ) ) );
1168  break;
1169  }
1170  else
1171  {
1172  // remove the invalid entry from the queue
1173  s_DirSizeJobQueue.pop_front();
1174  }
1175  }
1176 }
1177 
1178 #include "kmfoldermaildir.moc"
void 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:872
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
TQString fileName() const
Get/set filename in mail folder.
Definition: kmmessage.h:802
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:353
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:2341
KMMsgStatus status() const
Status of the message.
Definition: kmmessage.h:830
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:2291
void setComplete(bool v)
Set if the message is a complete message.
Definition: kmmessage.h:869
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:225
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:932
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:404
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:316
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:67
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:2319
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.