/*
**  LocalFolder.m
**
**  Copyright (c) 2001, 2002, 2003
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**          Ujwal S. Sathyam <ujwal@setlurgroup.com>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <Pantomime/LocalFolder.h>

#include <Pantomime/Constants.h>
#include <Pantomime/Flags.h>
#include <Pantomime/InternetAddress.h>
#include <Pantomime/LocalFolderCacheManager.h>
#include <Pantomime/LocalMessage.h>
#include <Pantomime/LocalStore.h>
#include <Pantomime/MimeMultipart.h>
#include <Pantomime/MimeUtility.h>
#include <Pantomime/NSData+Extensions.h>
#include <Pantomime/NSRegEx.h>
#include <Pantomime/Parser.h>
#include <Pantomime/NSString+Extensions.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSHost.h>
#include <Foundation/NSPathUtilities.h>

#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h> 
#include <time.h>
#include <unistd.h>

#ifndef LOCK_SH
#define LOCK_SH 1
#endif
#ifndef LOCK_EX
#define LOCK_EX 2
#endif 
#ifndef LOCK_NB
#define LOCK_NB 4
#endif
#ifndef LOCK_UN
#define LOCK_UN 8
#endif

//
//
//
@implementation LocalFolder

- (id) initWithPathToFile: (NSString *) thePath
{
  LocalFolderCacheManager *aLocalFolderCacheManager;
  NSDictionary *attributes;
  NSString *pathToCache, *localPath;
  BOOL bIsLocal;
  NSFileManager *aFileManager;
  
  self = [super initWithName: [thePath lastPathComponent]];

  // We verify if a <name>.tmp was present. If yes, we simply remove it.
  if ( [[NSFileManager defaultManager] fileExistsAtPath: [thePath stringByAppendingString: @".tmp"]] )
    {
      // NSDebugLog(@"Removed %@", [thePath stringByAppendingString: @".tmp"]);
      [[NSFileManager defaultManager] removeFileAtPath: [thePath stringByAppendingString: @".tmp"]
				      handler: nil];
    }

  [self setPath: thePath];
  
  NSDebugLog(@"Opening %@...", [self path]);
  
  // We set our initial file attributes for the mail store
  aFileManager = [NSFileManager defaultManager];
  localPath = [NSString stringWithFormat: @"%@/new", [self path]];
  
  if ([aFileManager fileExistsAtPath: localPath  isDirectory: &bIsLocal] && bIsLocal)
    {
      attributes = [aFileManager fileAttributesAtPath: [self path] traverseLink: NO];
      [self setFolderType: MAILBOX_FORMAT_MAILDIR];
    }
  else
    {
      attributes = [[NSFileManager defaultManager] fileAttributesAtPath: [self path]
						   traverseLink: NO];
      [self setFolderType: MAILBOX_FORMAT_MBOX];
    }

  [self setFileAttributes: attributes];
  

  if ( ([self folderType] == MAILBOX_FORMAT_MBOX) && ! [self _openAndLockFolder: [self path]] )
    {
      AUTORELEASE(self);
      return nil;
    }
  
  // We load our cache
  pathToCache = [NSString stringWithFormat: @"%@/.%@.cache",
			  [[self path] substringToIndex: 
					 ([[self path] length] - [[[self path] lastPathComponent] length])],
			  [[self path] lastPathComponent] ];
    
  // We load our cache from the file, creating it if it doesn't exist
  aLocalFolderCacheManager = [LocalFolderCacheManager localFolderCacheFromDiskWithPath: pathToCache];
  [self setCacheManager: aLocalFolderCacheManager];
  
  // We update the path to this folder for the cache manager
  [[self cacheManager] setPathToFolder: [self path]];
  
  NSDebugLog(@"Folder (%@) opened...", [self path]);

  return self;
}


//
//
//
- (void) dealloc
{
  RELEASE(fileAttributes);
  RELEASE(path);
  RELEASE(mailFilename);
  
  [super dealloc];
}


//
// This method is used to parse the message headers (and only that)
// from the current folder.
//
- (void) parse
{
  NSAutoreleasePool *pool;
  NSString *aPath;
  
  int numCurMessages, numNewMessages, numTmpMessages;
  BOOL bUseCache;

  numCurMessages = numNewMessages = numTmpMessages = 0;
  bUseCache = YES;
  
  //
  // We first verify if we need to parse the folder.
  // We invalidate our cache if our size OR or modification date have changed
  // For local, we check the number of messages in the "new" and cur sub-directories.
  bUseCache = [[[self fileAttributes] objectForKey: NSFileModificationDate] isEqualToDate: [[self cacheManager] modificationDate]] || [[[self fileAttributes] objectForKey: NSFileSize] intValue] == [(LocalFolderCacheManager *)[self cacheManager] fileSize];
  
  if ([self folderType] == MAILBOX_FORMAT_MAILDIR)
    {
      // Count the messages in the "cur" sub-directory
      aPath = [NSString stringWithFormat: @"%@/cur", [self path]];
      numCurMessages = [[[NSFileManager defaultManager] directoryContentsAtPath: aPath] count];
      
      // Count the messages in the "new" sub-directory
      aPath = [NSString stringWithFormat: @"%@/new", [self path]];
      numNewMessages = [[[NSFileManager defaultManager] directoryContentsAtPath: aPath] count];
      
      // Count the messages in the "tmp" sub-directory
      aPath = [NSString stringWithFormat: @"%@/tmp", [self path]];
      numTmpMessages = [[[NSFileManager defaultManager] directoryContentsAtPath: aPath] count];	
      
      // Compare with cache
      if (numCurMessages != [[[self cacheManager] messages] count])
	{
	  bUseCache = NO;		
	}
    }
  
  if ( bUseCache )
    {
      NSArray *array;
      int i;
          
      // In a local, if there are new messages, add them to the cache
      if (numNewMessages > 0 || numTmpMessages > 0)
	{
	  // We create a temporary autorelease pool since parse can be
	  // memory consuming on our default autorelease pool.
	  pool = [[NSAutoreleasePool alloc] init];
	  [self _parseMaildir: @"new"];
	  [self _parseMaildir: @"tmp"];
	  RELEASE(pool);
	}
      
      array = [[self cacheManager] messages];
      
      for (i = 0; i < [array count]; i++)
	{
	  [[array objectAtIndex: i] setFolder: self];
	}
      
      [self setMessages: array];
      
      return;
    }
  else
    {
      // NSDebugLog(@"Invalidating cache.");
      [[self cacheManager] invalidate];
    }
  
  NSDebugLog(@"Rebuilding cache for folder %@", [self name]);
  NSDebugLog(@"PLEASE, BE PATIENT!");
  
  // We create a temporary autorelease pool since parse can be
  // memory consuming on our default autorelease pool.
  pool = [[NSAutoreleasePool alloc] init];
  
  // Parse the mail store. For mbox, it will be one file.
  // For local, there will be a file for each message in the "cur" and "new"
  // sub-directories.
  switch ([self folderType])
    {
    case MAILBOX_FORMAT_MAILDIR:
      [self _parseMaildir: @"cur"];
      [self _parseMaildir: @"new"];
      break;
    case MAILBOX_FORMAT_MBOX:
    default:
      [self _parseMailFile: [self path] fileStream: [self stream] index: 0];
      break;
    }
  
  RELEASE(pool);
}


//
// This method is used to unfold the lines that have been folded
// by starting with the first line.
//
- (NSData *) unfoldLinesStartingWith: (char *) firstLine 
			  fileStream: (FILE*) theStream
{
  NSMutableData *aMutableData;
  NSData *aData;
  char aLine[1024], buf[1024];
  long mark;
  
  // We initialize our buffers
  memset(aLine, 0, 1024);
  memset(buf, 0, 1024);
    
  mark = ftell(theStream);
  fgets(aLine, 1024, theStream);

  if (aLine == NULL)
    {
      return [NSData dataWithBytes: firstLine  length: strlen(firstLine)];
    }

  // We create our mutable data
  aMutableData = [[NSMutableData alloc] initWithCapacity: strlen(firstLine)];

  // We remove the trailing \n and we append our first line to our mutable data
  strncpy(buf, firstLine, strlen(firstLine) - 1);
  [aMutableData appendCFormat: @"%s ", buf];
  
  // We loop as long as we have a space or tab character as the first character 
  // of the line that we just read
  while ( aLine[0] == 9 || aLine[0] == 32 )
    {
      char *ptr;

      // We skip the first char
      ptr = aLine;
      ptr++;
      
      // We init our buffer and we copy the data into it by trimming the trailing \n
      memset(buf, 0, 1024);
      strncpy(buf, ptr, strlen(ptr) - 1);
      [aMutableData appendCFormat: @"%s ", buf];

      // We set our mark and get the next folded line (if there's one)
      mark = ftell(theStream);
      memset(aLine, 0, 1024);
      fgets(aLine, 1024, theStream);
      
      if (aLine == NULL)
        {
	  RELEASE(aMutableData);
          return nil;
        }
    }

  // We reset our file pointer position and we free our C buffers.
  fseek(theStream, mark, SEEK_SET);
  
  // We trim our last " " that we added to our data
  aData = [aMutableData subdataToIndex: [aMutableData length] - 1];
  
  RELEASE(aMutableData);

  return aData;
}


//
// This method is used to close the current folder.
// It creates a temporary file where the folder is written to and
// it replaces the current folder file by this one once everything is
// alright.
//
- (void) close
{
  LocalStore *aLocalStore = nil;

  // We first obtain a reference to our local store
  aLocalStore = (LocalStore *)[self store];
  
  // We close the current folder
  // NSDebugLog(@"Closing %@...", [self name]);
  if ([self folderType] == MAILBOX_FORMAT_MBOX)
    {
      fclose([self stream]);
      flock([self fd], LOCK_UN);
      close([self fd]);	  
    }
  
  // We synchorize our cache one last time
  [[self cacheManager] synchronize];

  // We remove our current folder from the list of opened folders in the store
  [aLocalStore removeFolderFromOpenedFolders: self];
}


//
// This method permanently removes messages that have the flag DELETED.
//
// This method returns all messages that have the flag DELETED.
// All the returned message ARE IN RAW SOURCE.
//
- (NSArray *) expunge: (BOOL) returnDeletedMessages
{
  switch ([self folderType])
    {
    case MAILBOX_FORMAT_MBOX:
      return ([self _expungeMBOX: returnDeletedMessages]);
      break;
    case MAILBOX_FORMAT_MAILDIR:
      return ([self _expungeMAILDIR: returnDeletedMessages]);
      break;
    }
  return (nil);
}


//
// access / mutation methods
//

//
// This method returns the file descriptor used by this local folder.
//
- (int) fd
{
  return fd;
}


//
// This method sets the file descriptor to be used by this local folder.
//
- (void) setFD: (int) theFD
{
  fd = theFD;
}


//
//
//
- (NSString *) path
{
  return path;
}


- (void) setPath: (NSString *) thePath
{
  RETAIN(thePath);
  RELEASE(path);
  path = thePath;
}


//
// This method returns the file stream used by this local folder.
//
- (FILE *) stream
{
  return stream;
}


//
// This method sets the file stream to be used by this local folder.
//
- (void) setStream: (FILE *) theStream
{
  stream = theStream;
}


//
// This method returns the name of the mail file currently being processed.
//
- (NSString *) mailFilename
{
  return mailFilename;
}


//
// This method sets the mail file name to be processed.
//
- (void) setMailFilename: (NSString *) theFilename
{

  if ( theFilename )
    {
      RETAIN(theFilename);
      RELEASE(mailFilename);
      mailFilename = theFilename;
    }
  else
    {
      DESTROY(mailFilename);
    }
}


//
//
//
- (NSDictionary *) fileAttributes
{
  return fileAttributes;
}


- (void) setFileAttributes: (NSDictionary *) theAttributes
{
  RETAIN(theAttributes);
  RELEASE(fileAttributes);
  fileAttributes = theAttributes;
}


//
//
//
- (int) folderType
{
  return folderType;
}

- (void) setFolderType: (int) thisType
{
  folderType = thisType;
}


//
//
//
- (int) mode
{
  return PantomimeReadWriteMode;
}


//
// This method is used to append a message to this folder. The message
// must be specified in raw source. The message is appended to the 
// local file and is initialized after.
//
- (void) appendMessageFromRawSource: (NSData *) theData
                              flags: (Flags *) theFlags
{
  NSString *aMailFile, *aMailFilePath;
  NSMutableData *aMutableData;
  NSAutoreleasePool *pool;
  LocalMessage *aMessage;
  NSRange aRange;
  FILE *aStream;
  
  long mark, filePosition, bodyFilePosition;

  pool = [[NSAutoreleasePool alloc] init];

  aMutableData = [[NSMutableData alloc] initWithData: theData];
  aMailFile = nil;
  aStream = NULL;
  
  NSDebugLog(@"Appending message to %@", [self path]);


  // Set the appropriate stream
  if ( [self folderType] == MAILBOX_FORMAT_MAILDIR )
    {
      NSString *uniquePattern;
      NSMutableString *info;

      // Generate a unique file name
      uniquePattern = [NSString stringWithFormat: @"%d.%d_%d.%@",
				time(NULL), 
				getpid(),
				[[[self cacheManager] messages] count],
				[[NSHost currentHost] name]];
      
      // Generate the info field representing the status
      info = [[NSMutableString alloc] initWithString: @"2,"];
      
      if ([theFlags contain: DRAFT])
	{
	  [info appendString: @"D"];
	}
      
      if ([theFlags contain: FLAGGED])
	{
	  [info appendString: @"F"];
	}
      
      if ([theFlags contain: ANSWERED])
	{
	  [info appendString: @"R"];
	}

      if ([theFlags contain: SEEN])
	{
	  [info appendString: @"S"];
	}
      
      if ([theFlags contain: DELETED])
	{
	  [info appendString: @"T"];
	}
      
      // build the new file name
      aMailFile = [NSString stringWithFormat: @"%@:%@", uniquePattern, info];
      RELEASE(info);      
      
      // We need to put it in the tmp directory first.
      aMailFilePath = [NSString stringWithFormat: @"%@/tmp/%@", [self path], aMailFile];
      
      NSDebugLog(@"Unique file name = %@", aMailFile);
      
      aStream = fopen([aMailFilePath cString], "w+");
      
      if ( !aStream )
	{
	  RELEASE(pool);
	  return;
	}
    }
  else
    {
      aStream = [self stream];
      aMailFilePath = [self path];
    }

  // We now create the message from the raw source by using only the headers.
  aRange = [aMutableData rangeOfCString: "\n\n"];
  aMessage = [[LocalMessage alloc] initWithHeadersFromData: [MimeUtility unfoldLinesFromData: 
									   [aMutableData subdataToIndex: 
											   aRange.location + 1]]]; 
  
  // We keep the position where we were in the file
  mark = ftell( aStream );
  
  // If the message doesn't contain the "From ", we add it
  if ( ![aMutableData hasCPrefix: "From "] && 
       [self folderType] == MAILBOX_FORMAT_MBOX )
    {
      NSString *aSender, *aString;
      NSCalendarDate *aDate;

      // We get a valid sender
      if ( [aMessage from] && [[aMessage from] address] )
	{
	  aSender = [[aMessage from] address];
	}
      else
	{
	  aSender = @"unknown";
	}

      // We get a valid delivery date
      aDate = [aMessage receivedDate];

      if ( !aDate )
	{
	  aDate = [NSCalendarDate calendarDate];
	}
      

      // We must add our mbox delimiter
      aString = [NSString stringWithFormat: @"From %@ %@\n", aSender, 
			  [aDate descriptionWithCalendarFormat: @"%a %b %d %H:%M:%S %Y"]];
      [aMutableData insertCString: [aString cString]
		    atIndex: 0];
    }
  
  // We MUST replace every "\nFrom " in the message by "\n>From "
  aRange = [aMutableData rangeOfCString: "\nFrom "];
  
  while (aRange.location != NSNotFound)
    {
      [aMutableData replaceBytesInRange: aRange
		    withBytes: "\n>From "];
      
      aRange = [aMutableData rangeOfCString: "\nFrom "
			     options: 0
			     range: NSMakeRange(aRange.location + aRange.length,
						[aMutableData length] - aRange.location - aRange.length) ];
    }
  
  // We add our message separator to the end of the raw source of this message
  // It's a simple \n in the mbox format.
  [aMutableData appendCString: "\n"];
  
  // We go at the end of the file...
  if ( fseek(aStream, 0L, SEEK_END) < 0 )
    {
      NSException *anException;
     
      RELEASE(aMutableData);
      RELEASE(pool);
      anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
				 reason: @"Error in seeking to the end of the local folder."
				 userInfo: nil];
      [anException raise];
      return;
    }
  
  // We get the position of our message in the file
  filePosition = ftell( aStream );

  // We get the body position of the body
  aRange = [aMutableData rangeOfCString: "\n\n"];
  bodyFilePosition = filePosition + aRange.location + 2;
  
  // We write the string to our local folder
  if ( fwrite([aMutableData bytes], 1, [aMutableData length], aStream) <= 0 )
    {
      NSException *anException;
      
      RELEASE(aMutableData);
      RELEASE(aMessage);
      RELEASE(pool);
      anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
				 reason: @"Error in appending the raw source of a message to the local folder."
				 userInfo: nil];
      [anException raise];
      return;
    }

  [aMessage setFilePosition: filePosition];
  [aMessage setBodyFilePosition: bodyFilePosition];
  [aMessage setSize: (ftell(aStream) - filePosition) ];
  [aMessage setMessageNumber: ([self count] + 1)];
  [aMessage setFolder: self];
  [aMessage setMessageType: [self folderType]];
  
  // We set our flags
  if ( theFlags )
    {
      [aMessage setFlags: theFlags];
    }

  // If we are processing a maildir, close the stream and move the message into the "cur" directory.
  if ( [self folderType] == MAILBOX_FORMAT_MAILDIR )
    {
      NSString *curFilePath;
      
      fclose(aStream);
      curFilePath = [NSString stringWithFormat: @"%@/cur/%@", [self path], aMailFile];
      
      if ( [[NSFileManager defaultManager] movePath: aMailFilePath toPath: curFilePath handler: nil] == YES )
	{
	  aMailFilePath = curFilePath;
	  
	  // We enforce the file attribute (0600)
	  [(LocalStore *)[self store] enforceMode: 0600  atPath: aMailFilePath];
	}
      else
	{
	  NSDebugLog(@"Could not move %@ to %@", aMailFilePath, curFilePath);
	}  
    }
  
  [aMessage setMailFilename: aMailFilePath];
  
  
  // We append it to our folder
  [self appendMessage: aMessage];
  
  // We also append it to our cache
  if ( cacheManager )
    {
      [cacheManager addMessage: aMessage];
    }
  
  RELEASE(aMessage);
  
  // We finally reset our fp where the mark was set
  if  ([self folderType] != MAILBOX_FORMAT_MAILDIR )
    {
      fseek(aStream, mark, SEEK_SET);
    }
  
  RELEASE(aMutableData);
  RELEASE(pool);
}


//
//
//
- (NSArray *) search: (NSString *) theString
                mask: (int) theMask
             options: (int) theOptions
{
  NSMutableArray *aMutableArray;
  NSAutoreleasePool *pool;
  LocalMessage *aMessage;

  int i;

  aMutableArray = [[NSMutableArray alloc] init];
  pool = [[NSAutoreleasePool alloc] init];
  
  for (i = 0; i < [allMessages count]; i++)
    {
      aMessage = [allMessages objectAtIndex: i];
          
      //
      // We search inside the Message's content.
      //
      if ( theMask == PantomimeContent )
	{
	  BOOL messageWasInitialized, messageWasMatched;
	  
	  messageWasInitialized = [aMessage isInitialized];
	  messageWasMatched = NO;
	  
	  if ( !messageWasInitialized )
	    {
	      [aMessage setInitialized: YES];
	    }
	  
	  // We search recursively in all Message's parts
	  if ( [self _findInPart: (Part *)aMessage
		     string: theString
		     mask: theMask
		     options: theOptions] )
	    {
	      [aMutableArray addObject: aMessage];
	      messageWasMatched = YES;
	    }
	  
	  // We restore the message initialization status if the message doesn't match
	  if ( !messageWasInitialized && !messageWasMatched )
	    {
	      [aMessage setInitialized: NO];
	    }
	}
      //
      // We aren't searching in the content. For now, we search only in the Subject header value.
      //
      else
	{
	  NSString *aString;

	  aString = nil;

	  switch ( theMask )
	    {
	    case PantomimeFrom:
	      if ( [aMessage from] )
		{
		  aString = [[aMessage from] unicodeStringValue];
		}
	      break;
	      
	    case PantomimeTo:
	      aString = [MimeUtility stringFromRecipients: [aMessage recipients]
				     type: TO];
	      break;

	    case PantomimeSubject:
	    default:
	      aString = [aMessage subject];
	    }
	 
	  
	  if ( aString )
	    {
	      if ( (theOptions&PantomimeRegularExpression) )
		{
		  NSArray *anArray;
		  
		  anArray = [NSRegEx matchString: aString
				     withPattern : theString
				     isCaseSensitive: (theOptions&PantomimeCaseInsensitiveSearch)];
		  
		  if ( [anArray count] > 0 )
		    {
		      [aMutableArray addObject: aMessage];
		    }
		}
	      else
		{
		  NSRange aRange;
		  
		  if ( (theOptions&PantomimeCaseInsensitiveSearch) )
		    {
		      aRange = [aString rangeOfString: theString
					options: NSCaseInsensitiveSearch]; 
		    }
		  else
		    {
		      aRange = [aString rangeOfString: theString]; 
		    }
		  
		  if ( aRange.length > 0 )
		    {
		      [aMutableArray addObject: aMessage];
		    }
		}
	    }
	}
    } // for (i = 0; ...
	  
  RELEASE(pool);

  return AUTORELEASE(aMutableArray);
}

@end


//
// Private methods
//
@implementation LocalFolder (Private)

- (FILE *) _openAndLockFolder: (NSString *) thePath
{
  FILE *aStream;
  
  if ( !thePath )
    {
      return NULL;
    }

  fd = open([thePath cString], O_RDWR);
  
  if (fd < 0)
    {
      NSDebugLog(@"LocalFolder: Unable to get folder descriptor...");
      return NULL;
    }
  
  [self setMailFilename: thePath];
  
  if (flock(fd, LOCK_EX|LOCK_NB) < 0) 
    {
      NSDebugLog(@"LocalFolder: Unable to obtain the lock on the folder descriptor...");
      return NULL;
    }
  else 
    {
      flock(fd, LOCK_UN);
    }
  
  aStream = fdopen(fd, "r+");
  stream = aStream;
  
  if (aStream == NULL)
    {
      NSDebugLog(@"LocalFolder: Unable to open the specified mailbox...");
      return NULL;
    }
  
  flock(fd, LOCK_EX|LOCK_NB);
  
  return aStream;
}


//
//
//
- (int) _parseMailFile: (NSString *) theFile fileStream: (FILE*) aStream index: (int) theIndex
{
  LocalMessage *aLocalMessage;
  long begin, end, size;
  char aLine[1024];
  int index;
  BOOL success = NO;
  
  // We initialize our variables
  aLocalMessage = [[LocalMessage alloc] init];
  begin = ftell(aStream);
  end = 0L;
  index = theIndex;
  
  while (fgets(aLine, 1024, aStream) != NULL)
    {
      switch ( tolower(aLine[0]) )
	{	  
	case 'c':
	  if (strncasecmp(aLine, "Cc", 2) == 0)
	    {
	      [Parser parseDestination: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      forType: CC
		      inMessage: aLocalMessage];
	    }

	  break;
	  
	case 'd':
	  if (strncasecmp(aLine, "Date", 4) == 0)
	    {
	      [Parser parseDate: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 'f':
	  if (strncasecmp(aLine, "From ", 5) == 0)
	    {
	      // do nothing, it's our message separator
	    }
	  else if (strncasecmp(aLine, "From", 4) == 0)
	    {
	      [Parser parseFrom: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 'i':
	  if (strncasecmp(aLine, "In-Reply-To", 11) == 0)
	    {
	      [Parser parseInReplyTo: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;

	case 'm':
	  if (strncasecmp(aLine, "Message-ID", 10) == 0)
	    {
	      [Parser parseMessageID: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  else if (strncasecmp(aLine, "MIME-Version", 12) == 0)
	    {
	      [Parser parseMimeVersion: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 'r':
	  if (strncasecmp(aLine, "References", 10) == 0)
	    {
	      [Parser parseReferences: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 's':
	  if (strncasecmp(aLine, "Status", 6) == 0)
	    {
	      [Parser parseStatus: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  else if (strncasecmp(aLine, "Subject", 7) == 0)
	    {
	      [Parser parseSubject: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 't':
	  if (strncasecmp(aLine, "To", 2) == 0)
	    {
	      [Parser parseDestination: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      forType: TO
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case 'x':
	  if (strncasecmp(aLine, "X-Status", 8) == 0)
	    {
	      [Parser parseXStatus: [self unfoldLinesStartingWith: aLine  fileStream: aStream]
		      inMessage: aLocalMessage];
	    }
	  break;
	  
	case '\n':
	  
	  [aLocalMessage setFilePosition: begin];
	  [aLocalMessage setBodyFilePosition: ftell(aStream)];
	  
	  // We must set this in case the last message of our mbox is
	  // an "empty message", i.e, a message with all the headers but
	  // with an empty content.
	  end = ftell(aStream);
	  
	  while (fgets(aLine, 1024, aStream) != NULL)
	    {
	      if (strncmp(aLine, "From ", 5) == 0) break;
	      else end = ftell(aStream);
	    }
	  
	  fseek(aStream, end, SEEK_SET);
	  size = end - begin;
	  
	  
	  // We increment our index
	  index = index + 1;
	  
	  // We set the properties of our message object and we add it to our folder.
	  [aLocalMessage setSize: size];
	  [aLocalMessage setMessageNumber: index];
	  [aLocalMessage setFolder: self];
	  [aLocalMessage setMessageType: [self folderType]];
	  [aLocalMessage setMailFilename: theFile];
	  [self appendMessage: aLocalMessage];

	  // if we are reading a maildir message, check for flag information in the file name
	  if ([self folderType] == MAILBOX_FORMAT_MAILDIR)
	    {
	      NSString *uniquePattern, *info;
	      int indexOfPatternSeparator;
	      
	      // name of file will be unique_pattern:info with the status flags in the info field
	      indexOfPatternSeparator = [theFile indexOfCharacter: ':'];
	      
	      if (indexOfPatternSeparator > 1)
		{
		  uniquePattern = [theFile substringToIndex: indexOfPatternSeparator];
		  info = [theFile substringFromIndex: indexOfPatternSeparator];
		}
	      else
		{
		  uniquePattern = theFile;
		  info = @"";
		}
	      
	      // remove all the flags and rebuild
	      [[aLocalMessage flags] removeAll];
	      
	      if ([info indexOfCharacter: 'S'] >= 0)
		{
		  [[aLocalMessage flags] add: SEEN];
		}
	      
	      if ([info indexOfCharacter: 'R'] >= 0)
		{
		  [[aLocalMessage flags] add: ANSWERED];
		}
	      
	      if ([info indexOfCharacter: 'F'] >= 0)
		{
		  [[aLocalMessage flags] add: FLAGGED];
		}
	      
	      if ([info indexOfCharacter: 'D'] >= 0)
		{
		  [[aLocalMessage flags] add: DRAFT];
		}
	      
	      if ([info indexOfCharacter: 'T'] >= 0)
		{
		  [[aLocalMessage flags] add: DELETED];
		}
	    }		
	  
	  // We add to our cache
	  [[self cacheManager] addMessage: aLocalMessage];
	  success = YES;		
	  
	  RELEASE(aLocalMessage);
	  
	  begin = ftell(aStream);
	  
	  // We re-init our message and our mutable string for the next message we're gonna read
	  aLocalMessage = [[LocalMessage alloc] init];
	  break;
	  
	default:
	  break;
	}
    }

  
  // We sync our cache
  [[self cacheManager] synchronize];
  
  RELEASE(aLocalMessage);

  if(success == NO)
      NSLog(@"Failed to parse mail file %@!", theFile);
  
  // Return the position of the last message read in the cache list.
  return (index-1);
}


//
// This parses a local structure for messages by looking in the "cur" and "new" sub-directories.
//
- (BOOL) _parseMaildir: (NSString *) theDir
{
  NSMutableArray *mailFiles;
  NSFileManager *aFileManager;
  NSString *aPath, *thisMailFile;
  int i, fileCount;
  int numMessages, messageIndex;
  FILE *aStream;
  
  if (theDir == nil)
    {
      return NO;
    }
  
  // Get our current count of messages
  numMessages = [[[self cacheManager] messages] count];
  
  aFileManager = [NSFileManager defaultManager];

  // Read the directory
  aPath = [NSString stringWithFormat: @"%@/%@", [self path], theDir];
  mailFiles = [[NSMutableArray alloc] initWithArray: [aFileManager directoryContentsAtPath: aPath]];
  AUTORELEASE(mailFiles);

  // We remove Apple Mac OS X .DS_Store file
  [mailFiles removeObject: @".DS_Store"];
  fileCount = [mailFiles count];
  
  NSDebugLog(@"Found %d messages in %@", fileCount, theDir);
  
  if ( mailFiles != nil &&
       fileCount > 0)
    {
      for (i = 0; i < fileCount; i++)
	{
	  // NSDebugLog(@"Checking mail file %@", [mailFiles objectAtIndex: i]);
	  thisMailFile = [NSString stringWithFormat: @"%@/%@", aPath, [mailFiles objectAtIndex: i]];
	  aStream = fopen([thisMailFile cString], "r");
	  if ( !aStream )
	    {
	      continue;
	    }
	  
	  [self setMailFilename: thisMailFile];
	  messageIndex = [self _parseMailFile: thisMailFile fileStream: aStream index: numMessages];
	  
	  if (messageIndex >= 0)
	    {
	      numMessages++;
	    }
	  fclose(aStream);
	  
	  if (messageIndex < 0)
	    {
	      continue;
	    }
	  
	  // If we read this from the "new" or "tmp" sub-directories, move it to the "cur" directory
	  if ([theDir isEqualToString: @"new"] || [theDir isEqualToString: @"tmp"])
	    {
	      NSString *newPath;
	      LocalMessage *aLocalMessage;
	      
	      newPath = [NSString stringWithFormat: @"%@/cur/%@", [self path], [mailFiles objectAtIndex: i]];
	      
	      if ([aFileManager movePath: thisMailFile toPath: newPath handler: nil] == YES)
		{
		  aLocalMessage = [[[self cacheManager] messages] objectAtIndex: messageIndex];
		 
		  if (aLocalMessage)
		    {
		      [aLocalMessage setMailFilename: newPath];
		    }
		}
	      else
		{
		  NSDebugLog(@"Could not move %@ to %@", thisMailFile, newPath);
		}
	    }
	  
	}
    }
  
  return YES;
}


//
// Expunges a mbox file
//
- (NSArray *) _expungeMBOX: (BOOL) returnDeletedMessages
{
  NSMutableArray *aMutableArray;
  
  FILE *theInputStream, *theOutputStream;
  LocalStore *aLocalStore;
  
  LocalMessage *aMessage;
  Flags *theFlags;
  
  BOOL writeWasSuccessful, seenStatus, seenXStatus, doneWritingHeaders;
  NSString *pathToMailbox;
  
  int i, messageNumber;
  char aLine[1024];
  
  // We first obtain a reference to our local store
  aLocalStore = (LocalStore *)[self store];
  
  pathToMailbox = [NSString stringWithFormat: @"%@/%@", [aLocalStore path], [self name]];
  
  // The stream is used to store (temporarily) the new local folder
  theOutputStream = fopen([[NSString stringWithFormat: @"%@.tmp", pathToMailbox] cString], "a");
  theInputStream = [self stream];

  // We assume that our write operation was successful and we initialize our messageNumber to 1
  writeWasSuccessful = YES;
  messageNumber = 1;
  
  // We verify it the creation failed
  if ( !theOutputStream )
    {
      return [NSArray array];
    }
  
  aMutableArray = [[NSMutableArray alloc] init];
  
  for (i = 0; i < [allMessages count]; i++)
    {
      aMessage = [allMessages objectAtIndex: i];
      theFlags = [aMessage flags];

      doneWritingHeaders = seenStatus = seenXStatus = NO;    
      
      if ( [theFlags contain: DELETED] )
	{
	  // We add our message to our array of deleted messages, if we need to.
	  if ( returnDeletedMessages )
	    {
	      [aMutableArray addObject: [aMessage rawSource]];
	    }
	  
	  [(LocalFolderCacheManager *)[self cacheManager] removeMessage: aMessage];
	}
      else
	{
	  long delta, position, size;
	  int headers_length;
	  
	  // We get our position and headers_length
	  position = ftell(theOutputStream);
	  headers_length = 0;
	  
	  // We seek to the beginning of the message
	  fseek(theInputStream, [aMessage filePosition], SEEK_SET);
	 
	  size = [aMessage size];
	  memset(aLine, 0, 1024);
	  
	  while ( fgets(aLine, 1024, theInputStream) != NULL &&
		  (ftell(theInputStream) < ([aMessage filePosition] + size)) )
	    {
	      // We verify if we aren't finished reading our headers
	      if ( !doneWritingHeaders )
		{
		  // We check for the "null line" (ie., end of headers)
		  if ( strlen(aLine) == 1 && strcmp("\n", aLine) == 0 )
		    {
		      doneWritingHeaders = YES;
		      
		      if ( !seenStatus ) 
			{
			  fputs( [[NSString stringWithFormat: @"Status: %s\n",
					    [theFlags statusString]] cString], theOutputStream );
			}
		      
		      if ( !seenXStatus ) 
			{
			  fputs( [[NSString stringWithFormat: @"X-Status: %s\n",
						[theFlags xstatusString]] cString], theOutputStream );
			}

		      // Since we are done writing our headers, we update the headers_length variable
		      headers_length = ftell(theOutputStream) - position;

		      // We adjust the size of the message since headers might have been rewritten (Status/X-Status).
		      // We need the trailing -1 to actually remove the header/content separator (a single \n).
		      delta = headers_length - ([aMessage bodyFilePosition] - [aMessage filePosition] - 1);
		      
		      if ( delta > 0 )
			{
			  [aMessage setSize: (size+delta)];
			}
		    }
		  
		  // If we read the Status header, we replace it with the current Status header
		  if (strncasecmp(aLine,"Status:", 7) == 0) 
		    {
		      seenStatus = YES;
		      memset(aLine, 0, 1024);
		      sprintf(aLine, "Status: %s\n", [theFlags statusString]); 
		    }
		  else if (strncasecmp(aLine,"X-Status:", 9) == 0)
		    {
		      seenXStatus = YES;
		      memset(aLine, 0, 1024);
		      sprintf(aLine, "X-Status: %s\n", [theFlags xstatusString]); 
		    }
		}
	      
	      // We write our line to our new stream
	      if ( fputs(aLine, theOutputStream) < 0 )
		{
		  writeWasSuccessful = NO;
		  break;
		}
	      
	      memset(aLine, 0, 1024);
	    } // while (...)
	  
	  // We add our message separator
	  if ( fputs("\n", theOutputStream) < 0 )
	    {
	      writeWasSuccessful = NO;
	      break;
	    }
	    
	  // We update our message's ivars
	  [aMessage setFilePosition: position];
	  [aMessage setBodyFilePosition: (position+headers_length+1)];
	  [aMessage setMessageNumber: messageNumber];
	  
	  // We increment our messageNumber local variable
	  messageNumber++;
	}

    } // for (i = 0; i < count; i++)
  
  // We close our output stream
  if ( fclose(theOutputStream) != 0 )
    {
      writeWasSuccessful = NO;
    }
  
  //
  // We verify if the last write was successful, if yes, we remove our original mailbox
  // and we replace it by our temporary mailbox.
  //
  if ( writeWasSuccessful )
    {
      // We close the current folder
      fclose(theInputStream);
      flock([self fd], LOCK_UN);
      close([self fd]);
      
      // Now that Everything is alright, replace <folder name> by <folder name>.tmp
      [[NSFileManager defaultManager] removeFileAtPath: pathToMailbox
				      handler: nil];
      [[NSFileManager defaultManager] movePath: [NSString stringWithFormat: @"%@.tmp", pathToMailbox]
				      toPath: pathToMailbox
				      handler: nil];
      
      // We sync our cache
      [[self cacheManager] synchronize];
      
      // Now we re-open our folder and update the 'allMessages' ivar in the Folder superclass
      if ( ![self _openAndLockFolder: [self path]] )
	{
	  NSDebugLog(@"A fatal error occured in LocalFolder: -expunge.");
	}
      
      [self setMessages: [[self cacheManager] messages]];
    }
  
  //
  // The last write failed, let's remove our temporary file and keep the original mbox which, might
  // contains non-updated status flags or messages that have been transferred/deleted.
  //
  else
    {
      NSDebugLog(@"Writing to %@ failed. We keep the original mailbox.", pathToMailbox);
      NSDebugLog(@"This can be due to the fact that your partition containing this mailbox is full or that you don't have write permission in the directory where this mailbox is.");
      [[NSFileManager defaultManager] removeFileAtPath: [NSString stringWithFormat: @"%@.tmp", pathToMailbox]
				      handler: nil];
    }
  
  return AUTORELEASE(aMutableArray);	
}


//
// Expunges a maildir folder
//
- (NSArray *) _expungeMAILDIR: (BOOL) returnDeletedMessages
{
  NSMutableArray *aMutableArray;
  LocalMessage *aMessage;
  Flags *theFlags;
  int i, messageNumber;
  
  aMutableArray = [[NSMutableArray alloc] init];
  
  // We assume that our write operation was successful and we initialize our messageNumber to 1
  messageNumber = 1;
  
  for (i = 0; i < [allMessages count]; i++)
    {
      aMessage = [allMessages objectAtIndex: i];
      
      theFlags = [aMessage flags];
      
      if ( [theFlags contain: DELETED] )
	{
	  // We add our message to our array of deleted messages, if we need to.
	  if ( returnDeletedMessages )
	    {
	      [aMutableArray addObject: [aMessage rawSource]];
	    }
	  
	  // Delete the message file
	  [[NSFileManager defaultManager] removeFileAtPath: [aMessage mailFilename]  handler: nil];
	  
	  // Remove the message from the cache
	  [(LocalFolderCacheManager *)[self cacheManager] removeMessage: aMessage];			
	}
      else
	{
	  // rewrite the message to account for changes in the flags
	  BOOL moveWasSuccessful;
	  NSString *uniquePattern, *newFileName;
	  NSMutableString *info;
	  int indexOfPatternSeparator;
	  Flags *theFlags;
	  

	  // We update our message's ivars (folder and size don't change)
	  [aMessage setMessageNumber: messageNumber];

	  // We increment our messageNumber local variable
	  messageNumber++;

	  // we rename the message according to the maildir spec by appending the status information the name
	  // name of file will be unique_pattern:info with the status flags in the info field
	  indexOfPatternSeparator = [[aMessage mailFilename] indexOfCharacter: ':'];
	  
	  if (indexOfPatternSeparator > 1)
	    {
	      uniquePattern = [[aMessage mailFilename] substringToIndex: indexOfPatternSeparator];
	    }
	  else
	    {
	      uniquePattern = [aMessage mailFilename];
	    }

	  // build the info field
	  info = [[NSMutableString alloc] initWithString: @"2,"];
	  
	  theFlags = [aMessage flags];
	  
	  if ([theFlags contain: DRAFT])
	    {
	      [info appendString: @"D"];
	    }
	  
	  if ([theFlags contain: FLAGGED])
	    {
	      [info appendString: @"F"];
	    }
	  
	  if ([theFlags contain: ANSWERED])
	    {
	      [info appendString: @"R"];
	    }
	  
	  if ([theFlags contain: SEEN])
	    {
	      [info appendString: @"S"];
	    }
	  
	  if ([theFlags contain: DELETED])
	    {
	      [info appendString: @"T"];
	    }
	  
	  // build the new file name
	  newFileName = [NSString stringWithFormat: @"%@:%@", uniquePattern, info];
	  RELEASE(info);

	  // rename the message file
	  moveWasSuccessful = [[NSFileManager defaultManager] movePath: [aMessage mailFilename] toPath: newFileName handler: nil];
	  
	  if (moveWasSuccessful)
	    {
	      [aMessage setMailFilename: newFileName];
	    }
	}
    }
    
    // We sync our cache
    [[self cacheManager] synchronize];
        
    [self setMessages: [[self cacheManager] messages]];
    
  return AUTORELEASE(aMutableArray);	
}


//
//
//
- (BOOL) _findInPart: (Part *) thePart
	      string: (NSString *) theString
		mask: (int) theMask
             options: (int) theOptions
  
{  
  if ( [[thePart content] isKindOfClass:[NSString class]] )
    {
      // The part content is text; we perform the search      
      if ( (theOptions&PantomimeRegularExpression) )
	{
	  // The search pattern is a regexp

	  NSArray *anArray;
	  
	  anArray = [NSRegEx matchString: (NSString *)[thePart content]
			     withPattern : theString
			     isCaseSensitive: (theOptions&PantomimeCaseInsensitiveSearch)];
		  
	  if ( [anArray count] > 0 )
	    {
	      return YES;
	    }
	}
      else
	{
	  NSRange range;

	  if ( !(theOptions&PantomimeCaseInsensitiveSearch) )
	    {
	      range = [(NSString *)[thePart content] rangeOfString: theString
				   options: NSCaseInsensitiveSearch];
	    }
	  else
	    {
	      range = [(NSString *)[thePart content] rangeOfString: theString]; 
	    }
		  
	  if ( range.length > 0 )
	    {
	      return YES;
	    }
	}
    }
  
  else if ( [[thePart content] isKindOfClass: [Message class]] )
    {
      // The part content is a message; we parse it recursively
      return [self _findInPart: (Part *)[thePart content]
		   string: theString
		   mask: theMask
		   options: theOptions];
    }
  else if ( [[thePart content] isKindOfClass: [MimeMultipart class]] )
    {
      // The part content contains many part; we parse each part
      MimeMultipart *aMimeMultipart;
      Part *aPart;
      int i;
      
      aMimeMultipart = (MimeMultipart*)[thePart content];
      
      for (i = 0; i < [aMimeMultipart count]; i++)
	{
	  // We get our part
	  aPart = [aMimeMultipart bodyPartAtIndex: i];
	  
	  if ( [self _findInPart: (Part *)aPart
		     string: theString 
		     mask: theMask
		     options: theOptions] )
	    {
	      return YES;
	    }
	}
    }
  
  return NO;
}

@end
