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 <libkdepim/kfileio.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 <kapplication.h>
26 #include <kdebug.h>
27 #include <klocale.h>
28 #include <kstaticdeleter.h>
29 #include <kmessagebox.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)
136  rc = createIndexFromContents();
137  else
138  readIndex();
139  }
140  else
141  {
142  mAutoCreateIndex = false;
143  rc = createIndexFromContents();
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);
736  mMsgList.append( mi, mExportsSernums );
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 
893  correctUnreadMsgsCount();
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 
902  invalidateFolder();
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 KIO::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 += KApplication::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( KIO::Job* ) ),
1136  this, TQT_SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
1137  }
1138 
1139  return -1;
1140 }
1141 
1142 void KMFolderMaildir::slotDirSizeJobResult( KIO::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( KIO::Job* ) ),
1168  entry.first, TQT_SLOT( slotDirSizeJobResult( KIO::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"