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

LocalStore.m

/*
**  LocalStore.m
**
**  Copyright (c) 2001, 2002, 2003
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
**  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/LocalStore.h>

#include <Pantomime/Constants.h>
#include <Pantomime/LocalFolder.h>
#include <Pantomime/URLName.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSValue.h>

@implementation LocalStore

//
//
//
- (void) dealloc
{
  RELEASE(path);
  RELEASE(folders);
  RELEASE(fileManager);
  RELEASE(folderArray);
  
  [super dealloc];
}


//
//
//
- (id) initWithPathToDirectory: (NSString *) thePath
{
  BOOL isDirectory;
  
  self = [super init];
  
  [self setPath: thePath];
  
  // We initialize our cache of opened folders
  folders = [[NSMutableDictionary alloc] init];
  // Create an array to cache our folder structure
  folderArray = [[NSMutableArray alloc] initWithArray:
        [[fileManager enumeratorAtPath:
              [self path]] allObjects]];
  
   
  fileManager = [NSFileManager defaultManager];
  RETAIN(fileManager);
  
  if ( [fileManager fileExistsAtPath: [self path]
                isDirectory: &isDirectory] )
    {
      if ( !isDirectory )
      {
        AUTORELEASE(self);
        return nil;
      }
    }
  else
    {
      AUTORELEASE(self);
      return nil;
    }

  // Just before returning, we finally enforce our file attributes
  [self _enforceFileAttributes];


  return self;
}


//
//
//
- (id) initWithURL: (NSString *) theURL;
{
  URLName *aURLName;

  aURLName = [[URLName alloc] initWithString: theURL];
  
  self = [self initWithPathToDirectory: [aURLName path]];

  RELEASE(aURLName);
  
  return self;
}


//
// This method will open automatically Inbox (case-insensitive).
// It may return nil if the opening failed or Inbox wasn't found.
//
- (id) defaultFolder
{
  return [self folderForName: @"Inbox"];
}


//
// This method is used to open the folder theName in the current
// directory of this local store.
//
- (id) folderForName: (NSString *) theName
{
  NSEnumerator *anEnumerator;
  NSString *aString;
  Folder *cachedFolder;

  anEnumerator = [self folderEnumerator];
  cachedFolder = [folders objectForKey: theName];
  
  if ( [self folderForNameIsOpen: theName] )
    {
      return nil;
    }
  
  if ( !cachedFolder )
    {
      while ( (aString = [anEnumerator nextObject]) )
      {
        if ( [aString compare: theName] == NSOrderedSame )
          {
            LocalFolder *aFolder;

              aFolder = [[LocalFolder alloc] initWithPathToFile:
                                     [NSString stringWithFormat:@"%@/%@",
                                           [self path], aString]];

            if ( aFolder )
            {
              [aFolder setStore: (Store *)self];
              [aFolder setName: theName];
              [aFolder parse];

              // We now cache it and return it
              [folders setObject: AUTORELEASE(aFolder)
                     forKey: theName];
            }

            return aFolder;
          }
      }
      
      return nil; // Never reached?
    }
  else
    {
        return cachedFolder;
    }
}


//
//
//
- (id) folderForURL: (NSString *) theURL;
{
  URLName *urlName;
  id aFolder;

  urlName = [[URLName alloc] initWithString: theURL];

  aFolder = [self folderForName: [urlName foldername]];

  RELEASE(urlName);
  
  return aFolder;
}


//
// This method returns the list of folders contained in 
// a specific directory. It'll currently ignore some things
// like Netscape Mail's summary files and Pantomime's local
// cache files.
//
- (NSEnumerator *) folderEnumerator
{
  if ( [folderArray count] > 0 )
    {
      return [folderArray objectEnumerator];
    }

  return [self _rebuildFolderEnumerator];
}


//
//
//
- (NSEnumerator *) subscribedFolderEnumerator
{
  return [self folderEnumerator];
}


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


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


//
//
//
- (void) close
{
  NSEnumerator *anEnumerator;
  LocalFolder *aFolder;

  anEnumerator = [self openedFoldersEnumerator];
  
  while ( (aFolder = [anEnumerator nextObject]) )
    {
      [aFolder close];
    }
}


//
//
//
- (NSEnumerator *) openedFoldersEnumerator
{
  return [folders objectEnumerator];
}


//
//
//
- (void) removeFolderFromOpenedFolders: (Folder *) theFolder
{
  [folders removeObjectForKey: [(LocalFolder *)theFolder name]];
}


//
//
//
- (BOOL) folderForNameIsOpen: (NSString *) theName
{
  NSEnumerator *anEnumerator;
  LocalFolder *aFolder;
  
  anEnumerator = [self openedFoldersEnumerator];

  while ( (aFolder = [anEnumerator nextObject]) )
    {
      if ( [[aFolder name] compare: theName] == NSOrderedSame)
      {
        return YES;
      }
    }

  return NO;
}


//
//
//
- (int) folderTypeForFolderName: (NSString *) theName
{
  NSString *aString;
  BOOL isDir;

  aString = [NSString stringWithFormat: @"%@/%@", [self path], theName];
  
  [[NSFileManager defaultManager] fileExistsAtPath: aString
                          isDirectory: &isDir];
  
  if ( isDir )
    {
      // This could be a maildir store. Check for maildir specific subfolders.
      aString = [NSString stringWithFormat: @"%@/%@/cur", [self path], theName];
      
      if ( [[NSFileManager defaultManager] fileExistsAtPath: aString
                                 isDirectory: &isDir] && isDir )
      {
        return PantomimeHoldsMessages;
      }
      else
      {
        return PantomimeHoldsFolders;
      }
    }

  return PantomimeHoldsMessages;
}


//
//
//
- (NSString *) folderSeparator
{
  return @"/";
}


//
// theName must be the full path of the mailbox.
//
// Returns YES on success. NO otherwise.
//
- (BOOL) createFolderWithName: (NSString *) theName 
                   type: (int) theType
                 contents: (NSData *) theContents
{
  NSString *aName, *pathToFile;
  BOOL aBOOL, isDir;

  NSFileManager *aFileManager;
  NSEnumerator *anEnumerator;

  aFileManager = [NSFileManager defaultManager];
  anEnumerator = [self folderEnumerator];
  
  pathToFile = [NSString stringWithFormat: @"%@/%@", [self path], theName];
  pathToFile = [pathToFile substringToIndex: ([pathToFile length] - [[pathToFile lastPathComponent] length] - 1)];
  
  // We verify if the folder with that name does already exist
  while ( (aName = [anEnumerator nextObject]) )
    {
      if ([aName compare: theName
             options: NSCaseInsensitiveSearch] == NSOrderedSame)
      {
        return NO;
      }
    }
  
  // Ok, the folder doesn't already exist.
  // Check if we want to create a simple folder/directory.
  if (theType == MAILBOX_FORMAT_FOLDER)
    {
      NSString *aString;

      aString = [NSString stringWithFormat: @"%@/%@", [self path], theName];
      aBOOL = [aFileManager createDirectoryAtPath: aString  attributes: nil];
      
      if ( aBOOL )
      {
        [self enforceMode: 0700  atPath: aString];
        
        // rebuild the folder list
        [self _rebuildFolderEnumerator];
      }

      return aBOOL;
    }
  
  aBOOL = NO;

  // We want to create a mailbox store; check if it already exists.
  if ( [aFileManager fileExistsAtPath: pathToFile
                 isDirectory: &isDir] )
    {
      int size;
      
      size = [[[aFileManager fileAttributesAtPath: pathToFile
                       traverseLink: NO] objectForKey: NSFileSize] intValue];

      // If we got an empty file or simply a directory...
      if ( size == 0 || isDir )
      {
        NSString *aString;
        
        // If the size is 0, that means we have an empty file. We first convert this
        // file to a directory.
        if ( size == 0 )
          {
            [aFileManager removeFileAtPath: pathToFile  handler: nil];
            [aFileManager createDirectoryAtPath: pathToFile  attributes: nil];
          }
        
        // We can now proceed with the creation of our store.
        // Check the type of store we want to create
        switch (theType)
          {
          case MAILBOX_FORMAT_MAILDIR:
            // Create the main maildir directory
            aString = [NSString stringWithFormat: @"%@/%@", [self path], theName];  
            aBOOL = [aFileManager createDirectoryAtPath: aString  attributes: nil];
            [self enforceMode: 0700  atPath: aString];
                                                    
            // Now create the cur, new, and tmp sub-directories.
            aString = [NSString stringWithFormat: @"%@/%@/cur", [self path], theName];
            aBOOL = aBOOL & [aFileManager createDirectoryAtPath: aString  attributes: nil];
            [self enforceMode: 0700  atPath: aString];
            
            // new
            aString = [NSString stringWithFormat: @"%@/%@/new", [self path], theName];
            aBOOL = aBOOL & [aFileManager createDirectoryAtPath: aString  attributes: nil];
            [self enforceMode: 0700  atPath: aString];

            // tmp
            aString = [NSString stringWithFormat: @"%@/%@/tmp", [self path], theName];
            aBOOL = aBOOL & [aFileManager createDirectoryAtPath: aString  attributes: nil];
            [self enforceMode: 0700  atPath: aString];
            break;
            
          case MAILBOX_FORMAT_MBOX:
          default:
            aBOOL = [aFileManager createFileAtPath: [NSString stringWithFormat: @"%@/%@",
                                                [self path], theName]
                            contents: theContents
                            attributes: nil ];
            
            // We now enforce the mode (0600) on this new mailbox
            [self enforceMode: 0600
                atPath: [NSString stringWithFormat: @"%@/%@", [self path], theName]];
            break;                          
          }
        
        // rebuild the folder list
        [self _rebuildFolderEnumerator];
      }
      else
      {
        aBOOL = NO;
      }
    }
            
  return aBOOL;
}


//
// theName must be the full path of the mailbox.
//
// Returns YES on success. NO otherwise.
//
- (BOOL) deleteFolderWithName: (NSString *) theName
{
  NSFileManager *aFileManager;
  BOOL aBOOL, isDir;
  
  aFileManager = [NSFileManager defaultManager];
  aBOOL = NO;

  if ( [aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@", [self path], theName]
                 isDirectory: &isDir] )
    {
      if ( isDir )
      {
        NSEnumerator *theEnumerator;
        NSArray *theEntries, *dirContents;
        
        theEnumerator = [aFileManager enumeratorAtPath: [NSString stringWithFormat: @"%@/%@",
                                                    [self path], theName]];
        
        // FIXME - Verify the Store's path.
        // If it doesn't contain any mailboxes and it's actually not or Store's path, we remove it.
        theEntries = [theEnumerator allObjects];
        dirContents = [aFileManager directoryContentsAtPath: [NSString stringWithFormat: @"%@/%@",
                                                    [self path], theName]];
        if ( [theEntries count] == 0 )
          {
            aBOOL = [aFileManager removeFileAtPath: [NSString stringWithFormat: @"%@/%@",
                                                 [self path], theName]
                           handler: nil];
            
            // Rebuild the folder tree
            if(aBOOL == YES)
              [self _rebuildFolderEnumerator];

            return (aBOOL);

          }
        // We could also be trying to delete a maildir mailbox which
        // has a directory structure with 3 sub-directories: cur, new, tmp
        else if ( [aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/cur", [self path], theName]
                 isDirectory: &isDir])
          {
            // Make sure that these are the maildir directories and not something else.
            if ( ![aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/new", [self path], theName]
                          isDirectory: &isDir] )
            {
              return NO;
            }
            if ( ![aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/tmp", [self path], theName]
                          isDirectory: &isDir] )
            {
              return NO;
            }
          }
        else
          {
            return NO;
          }
      }

      // We remove the mbox or maildir store
      aBOOL = [aFileManager removeFileAtPath: [NSString stringWithFormat: @"%@/%@",
                                          [self path], theName]
                      handler: nil];
      
      // We remove the cache, if the store deletion was successful
      if ( aBOOL )
      {
        NSString *aString;

        aString = [theName lastPathComponent];
        
        [[NSFileManager defaultManager] removeFileAtPath: [NSString stringWithFormat: @"%@/%@.%@.cache",
                                                      [self path],
                                                      [theName substringToIndex:
                                                             ([theName length] - [aString length])],
                                                      aString]
                                handler: nil];
      }

        // Rebuild the folder tree
        [self _rebuildFolderEnumerator];
    }
  
  return aBOOL;
}


//
// theName and theNewName MUST be the full path of those mailboxes.
//
// Returns YES on success. NO otherwise.
//
- (BOOL) renameFolderWithName: (NSString *) theName
                       toName: (NSString *) theNewName
{
  NSFileManager *aFileManager;
  BOOL aBOOL, isDir;
  
  aFileManager = [NSFileManager defaultManager];
  aBOOL = NO;

  // We verify if the destination path exists. If it does, we abort the rename operation
  // since we don't want to overwrite the folder.
  if ( [aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@", [self path], theNewName]] )
    {
      return NO;
    }

  // We verify if the source path is valid
  if ( [aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@", [self path], theName]
                 isDirectory: &isDir] )
    {
      if ( isDir )
      {
        NSEnumerator *theEnumerator;
        NSArray *theEntries;
        
        theEnumerator = [aFileManager enumeratorAtPath: [NSString stringWithFormat: @"%@/%@",
                                                    [self path], theName]];
        
        // FIXME - Verify the Store's path.
        // If it doesn't contain any mailboxes and it's actually not or Store's path, we rename it.
        theEntries = [theEnumerator allObjects];
        
        if ( [theEntries count] == 0 )
          {
            return [aFileManager movePath: [NSString stringWithFormat: @"%@/%@",[self path], theName]
                           toPath:  [NSString stringWithFormat: @"%@/%@",  [self path], theNewName]
                           handler: nil];
          }
        // We could also be trying to delete a maildir mailbox which
        // has a directory structure with 3 sub-directories: cur, new, tmp
        else if ( [aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/cur", [self path], theName]
                          isDirectory: &isDir])
          {
            // Make sure that these are the maildir directories and not something else.
            if ( ![aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/new", [self path], theName]
                          isDirectory: &isDir] )
            {
              return NO;
            }
            if ( ![aFileManager fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/tmp", [self path], theName]
                          isDirectory: &isDir] )
            {
              return NO;
            }
        }
        else
          {
            return NO;
          }
      }
      
      // We rename the mail store
      aBOOL = [aFileManager movePath: [NSString stringWithFormat: @"%@/%@", [self path], theName]
                      toPath: [NSString stringWithFormat: @"%@/%@", [self path], theNewName]
                      handler: nil];
      
      // We remove the cache, if the store rename was successful
      if ( aBOOL )
      {
        NSString *str1, *str2;

        str1 = [theName lastPathComponent];
        str2 = [theNewName lastPathComponent];
        
        [[NSFileManager defaultManager] movePath: [NSString stringWithFormat: @"%@/%@.%@.cache",
                                                [self path],
                                                [theName substringToIndex:
                                                       ([theName length] - [str1 length])],
                                                str1]
                                toPath: [NSString stringWithFormat: @"%@/%@.%@.cache",
                                              [self path],
                                              [theNewName substringToIndex:
                                                        ([theNewName length] - [str2 length])],
                                              str2]
                                handler: nil];
      }
      
      // Rebuild the folder tree
      [self _rebuildFolderEnumerator];
    }
  
  return aBOOL;
}


//
//
//
- (void) enforceMode: (unsigned long) theMode
            atPath: (NSString *) thePath

{
  NSMutableDictionary *currentFileAttributes;
  
  unsigned long current_attributes, desired_attributes;
  
  
  currentFileAttributes = [[NSMutableDictionary alloc] initWithDictionary: [fileManager fileAttributesAtPath: thePath
                                                                  traverseLink: YES]];
  
  current_attributes = [currentFileAttributes filePosixPermissions];
  desired_attributes = theMode;
  
  if ( current_attributes != desired_attributes )
    {
      [currentFileAttributes setObject: [NSNumber numberWithUnsignedLong: desired_attributes]
                       forKey: NSFilePosixPermissions];
      
      [fileManager changeFileAttributes: currentFileAttributes
               atPath: thePath];
    }

  RELEASE(currentFileAttributes);
}


//
//
//
- (BOOL) isConnected
{
  return YES;
}

@end


//
// Private interface
//
@implementation LocalStore (Private)

- (void) _enforceFileAttributes
{
  NSEnumerator *anEnumerator;
  NSAutoreleasePool *pool;
  NSString *aString;

  pool = [[NSAutoreleasePool alloc] init];

  //
  // We verify if our Store path's mode is 0700
  //
  [self enforceMode: 0700  atPath: [self path]];
  
  //
  // We ensure that all subdirectories are using mode 0700 and files are using mode 0600
  //
  anEnumerator = [self folderEnumerator];

  while ( (aString = [anEnumerator nextObject]) )
    {
      BOOL isDir;
      
      aString = [NSString stringWithFormat: @"%@/%@", [self path], aString];
      
      if ( [fileManager fileExistsAtPath: aString
                  isDirectory: &isDir] )
      {
        if ( isDir )
          {
            [self enforceMode: 0700
                atPath: aString];
          }
        else
          {
            [self enforceMode: 0600
                atPath: aString];
          }
      }
    } 
 
  RELEASE(pool);
}


//
// Rebuild the folder hierarchy
//
- (NSEnumerator *) _rebuildFolderEnumerator
{
  NSString *aString, *lastPathComponent, *pathToFolder;     
  NSEnumerator *tmpEnumerator;
  NSArray *tmpArray;
  int i;
  
  // Clear out our cached folder structure and refresh from the file system
  [folderArray removeAllObjects];
  [folderArray setArray: [[fileManager enumeratorAtPath: [self path]] allObjects]];
  
  //
  // We iterate through our array. If mbox A and .A.summary (or .A.cache) exists, we
  // remove .A.summary (or .A.cache) from our mutable array.
  // We do this in two runs:
  // First run: remove maildir sub-directory structure so that is appears as a regular folder.
  // Second run: remove other stuff like *.cache, *.summary
  //
  for (i = 0; i< [folderArray count]; i++)
    {
      BOOL bIsMailDir;
      
      aString = [folderArray objectAtIndex: i];
      
      lastPathComponent = [aString lastPathComponent];
      pathToFolder = [aString substringToIndex: ([aString length] - [lastPathComponent length])];
    
      //
      // First run:
      // If this is a maildir directory, remove its sub-directory structure from the list,
      // so that the maildir store appears just like a regular mail store.
      //
      if ( [[NSFileManager defaultManager] fileExistsAtPath: [NSString stringWithFormat: @"%@/%@/cur", 
                                                       [self path], aString] 
                                 isDirectory: &bIsMailDir] && bIsMailDir )
      {
        NSDirectoryEnumerator *maildirEnumerator;
        NSArray *subpaths;
      
        // Wust ensure 700 mode un cur/new/tmp folders and 600 on all files (ie., messages)
        [self enforceMode: 0700
            atPath: [NSString stringWithFormat: @"%@/%@/cur", [self path], aString]];

        [self enforceMode: 0700
            atPath: [NSString stringWithFormat: @"%@/%@/new", [self path], aString]];
        
        [self enforceMode: 0700
            atPath: [NSString stringWithFormat: @"%@/%@/tmp", [self path], aString]];
        
        
        // Get all the children of this directory an remove them from our mutable array.
        maildirEnumerator = [[NSFileManager defaultManager] enumeratorAtPath: 
                                                [NSString stringWithFormat: @"%@/%@", 
                                                        [self path], aString]];
        
        subpaths = [[NSFileManager defaultManager] subpathsAtPath: [NSString stringWithFormat: @"%@/%@", 
                                                             [self path], aString]];
        [folderArray removeObjectsInRange: NSMakeRange(i+1,[subpaths count])];
      }
    }
  
  //
  // Second Run: Get rid of cache, summary and OS specific stuff
  //
  tmpArray = [[NSArray alloc] initWithArray: folderArray];
  AUTORELEASE(tmpArray);
  tmpEnumerator = [tmpArray objectEnumerator];
  
  while ( (aString = [tmpEnumerator nextObject]) )
    {
      lastPathComponent = [aString lastPathComponent];
      pathToFolder = [aString substringToIndex: ([aString length] - [lastPathComponent length])];
      
      // We remove Netscape/Mozilla summary file.
      [folderArray removeObject: [NSString stringWithFormat: @"%@.%@.summary", pathToFolder, lastPathComponent]];
      
      // We remove Pantomime's cache file. Before doing so, we ensure it's 600 mode.
      [folderArray removeObject: [NSString stringWithFormat: @"%@.%@.cache", pathToFolder, lastPathComponent]];
      [self enforceMode: 0600
          atPath: [NSString stringWithFormat: @"%@/%@.%@.cache", [self path], pathToFolder, lastPathComponent]];

      // We also remove Apple Mac OS X .DS_Store directory
      [folderArray removeObject: [NSString stringWithFormat: @"%@.DS_Store", pathToFolder]];
    }
  
  return [folderArray objectEnumerator];
}

@end

Generated by  Doxygen 1.6.0   Back to index