Logo Search packages:      
Sourcecode: pantomime version File versions  Download package

LocalFolder.m

/*
**  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, buf;
  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;
  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;
  
  // 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

Generated by  Doxygen 1.6.0   Back to index