]> err.no Git - sope/commitdiff
added NGImap4Connection API
authorhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Mon, 11 Jul 2005 12:24:23 +0000 (12:24 +0000)
committerhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Mon, 11 Jul 2005 12:24:23 +0000 (12:24 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@881 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

sope-mime/ChangeLog
sope-mime/NGImap4/ChangeLog
sope-mime/NGImap4/GNUmakefile
sope-mime/NGImap4/NGImap4Connection.h [new file with mode: 0644]
sope-mime/NGImap4/NGImap4Connection.m [new file with mode: 0644]
sope-mime/NGImap4/NGImap4ConnectionManager.h [new file with mode: 0644]
sope-mime/NGImap4/NGImap4ConnectionManager.m [new file with mode: 0644]
sope-mime/NGImap4/NGImap4MailboxInfo.h [new file with mode: 0644]
sope-mime/NGImap4/NGImap4MailboxInfo.m [new file with mode: 0644]
sope-mime/Version

index 2521dd0428f274e7cc718504506de84b4a9ab4d3..b1e9a5901d23a5590d9a527b159317495c018827 100644 (file)
@@ -1,3 +1,7 @@
+2005-07-11  Helge Hess  <helge.hess@opengroupware.org>
+
+       * NGImap4: added NGImap4Connection, NGImap4ConnectionManager (v4.5.223)
+
 2005-07-07  Helge Hess  <helge.hess@opengroupware.org>
 
        * NGImap4: added method to store flags for MSN sequences (v4.5.222)
index e2bbfe86593639087ac62a7d33e74a6916d127b8..aec76084abbade106cfab14c0fae8242d1f5b2ea 100644 (file)
@@ -1,3 +1,8 @@
+2005-07-11  Helge Hess  <helge.hess@opengroupware.org>
+
+       * added NGImap4Connection/NGImap4ConnectionManager classes, a simpler
+         interface to the IMAP4 client library
+
 2005-07-07  Helge Hess  <helge.hess@opengroupware.org>
 
        * NGImap4Client.m: added -storeFlags:forMSNs:addOrRemove: method to
index f2d59089e4f1b8185fe3299f76e5f70428f051a4..f534d97a7dec6c0f95a4a3ef0eeb55dfdf461f1b 100644 (file)
@@ -9,20 +9,23 @@ NGImap4_HEADER_FILES_DIR         = .
 NGImap4_HEADER_FILES_INSTALL_DIR = /NGImap4
 
 NGImap4_HEADER_FILES = \
-       NGImap4ResponseParser.h \
-       NGImap4Client.h         \
-       NGImap4Support.h        \
-       NGImap4Folder.h         \
-       NGImap4Context.h        \
-       NGImap4Message.h        \
-       NGImap4ServerRoot.h     \
-       NGImap4FileManager.h    \
-       NGImap4.h               \
-       NGImap4DataSource.h     \
-       NSString+Imap4.h        \
-       NGSieveClient.h         \
-       NGImap4Envelope.h       \
-       NGImap4EnvelopeAddress.h\
+       NGImap4ResponseParser.h         \
+       NGImap4Client.h                 \
+       NGImap4Support.h                \
+       NGImap4Folder.h                 \
+       NGImap4Context.h                \
+       NGImap4Message.h                \
+       NGImap4ServerRoot.h             \
+       NGImap4FileManager.h            \
+       NGImap4.h                       \
+       NGImap4DataSource.h             \
+       NSString+Imap4.h                \
+       NGSieveClient.h                 \
+       NGImap4Envelope.h               \
+       NGImap4EnvelopeAddress.h        \
+       NGImap4Connection.h             \
+       NGImap4MailboxInfo.h            \
+       NGImap4ConnectionManager.h      \
 
 NGImap4_OBJC_FILES = \
        NGImap4ResponseParser.m         \
@@ -49,6 +52,10 @@ NGImap4_OBJC_FILES = \
        NGImap4ResponseNormalizer.m     \
        NGImap4Envelope.m               \
        NGImap4EnvelopeAddress.m        \
+       \
+       NGImap4Connection.m             \
+       NGImap4MailboxInfo.m            \
+       NGImap4ConnectionManager.m      \
 
 -include GNUmakefile.preamble
 include $(GNUSTEP_MAKEFILES)/subproject.make
diff --git a/sope-mime/NGImap4/NGImap4Connection.h b/sope-mime/NGImap4/NGImap4Connection.h
new file mode 100644 (file)
index 0000000..4d86fc9
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __NGImap4Connection_H__
+#define __NGImap4Connection_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  NGImap4Connection
+  
+  A cached connection to an IMAP4 server plus some cached objects. Do not
+  instantiate this object directly but rather use the NGImap4ConnectionManager.
+  
+  This API is intended to be simpler and more consistent than NGImap4Client.
+  
+  It caches:
+   - the folder hierarchy
+   - uid sets?
+   - 'myrights' permissions of mailboxes
+   ?
+*/
+
+@class NSString, NSDate, NSArray, NSDictionary, NSURL, NSMutableDictionary;
+@class NSException, NSData;
+@class NGImap4Client;
+
+@interface NGImap4Connection : NSObject
+{
+@public
+  NGImap4Client *client;
+  NSString      *password;
+  NSDate        *creationTime;
+
+  /* hierarchy cache */
+  NSDictionary  *subfolders;
+
+  /* permission cache */
+  NSMutableDictionary *urlToRights;
+  
+  /* uids cache */
+  NSArray *cachedUIDs;
+  NSURL   *uidFolderURL;
+  id      uidSortOrdering;
+}
+
+- (id)initWithClient:(NGImap4Client *)_client password:(NSString *)_pwd;
+
+/* accessors */
+
+- (NGImap4Client *)client;
+- (BOOL)isValidPassword:(NSString *)_pwd;
+
+- (NSDate *)creationTime;
+
+- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy;
+- (NSDictionary *)cachedHierarchyResults;
+- (void)flushFolderHierarchyCache;
+
+- (id)cachedUIDsForURL:(NSURL *)_url qualifier:(id)_q sortOrdering:(id)_so;
+- (void)cacheUIDs:(NSArray *)_uids forURL:(NSURL *)_url
+  qualifier:(id)_q sortOrdering:(id)_so;
+
+- (NSString *)cachedMyRightsForURL:(NSURL *)_url;
+- (void)cacheMyRights:(NSString *)_rights forURL:(NSURL *)_url;
+
+- (void)flushMailCaches;
+
+/* folder operations */
+
+- (NSArray *)subfoldersForURL:(NSURL *)_url;
+- (NSArray *)allFoldersForURL:(NSURL *)_url;
+
+/* message operations */
+
+- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier
+  sortOrdering:(id)_so;
+- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
+  parts:(NSArray *)_parts;
+- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts;
+- (NSData *)fetchContentOfBodyPart:(NSString *)_partId atURL:(NSURL *)_url;
+
+/* message flags */
+
+- (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f toURL:(NSURL *)_url;
+- (NSException *)addFlags:(id)_f    toURL:(NSURL *)_u;
+- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u;
+- (NSException *)markURLDeleted:(NSURL *)_url;
+- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url;
+
+/* posting new data */
+
+- (NSException *)postData:(NSData *)_data flags:(id)_f toFolderURL:(NSURL *)_u;
+
+/* operations */
+
+- (NSException *)expungeAtURL:(NSURL *)_url;
+
+/* copying and moving */
+
+- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl;
+
+/* managing folders */
+
+- (BOOL)doesMailboxExistAtURL:(NSURL *)_url;
+- (id)infoForMailboxAtURL:(NSURL *)_url;
+- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url;
+- (NSException *)deleteMailboxAtURL:(NSURL *)_url;
+- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl;
+
+/* ACLs */
+
+- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url;
+- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url;
+
+@end
+
+#endif /* __NGImap4Connection_H__ */
diff --git a/sope-mime/NGImap4/NGImap4Connection.m b/sope-mime/NGImap4/NGImap4Connection.m
new file mode 100644 (file)
index 0000000..7a1d779
--- /dev/null
@@ -0,0 +1,875 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "NGImap4Connection.h"
+#include "NGImap4MailboxInfo.h"
+#include "NGImap4Client.h"
+#include "imCommon.h"
+
+@implementation NGImap4Connection
+
+static BOOL     debugOn    = NO;
+static BOOL     debugCache = NO;
+static BOOL     debugKeys  = NO;
+static BOOL     alwaysSelect     = NO;
+static BOOL     onlyFetchInbox   = NO;
+static NSString *imap4Separator  = nil;
+
++ (void)initialize {
+  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+  
+  debugOn      = [ud boolForKey:@"SOGoEnableIMAP4Debug"];
+  debugCache   = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"];
+  alwaysSelect = [ud boolForKey:@"SOGoAlwaysSelectIMAP4Folder"];
+  if (debugOn)    NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!");
+  if (alwaysSelect)
+    NSLog(@"WARNING: 'SOGoAlwaysSelectIMAP4Folder' enabled (slow down)");
+
+  imap4Separator = [[ud stringForKey:@"SOGoIMAP4StringSeparator"] copy];
+  if ([imap4Separator length] == 0)
+    imap4Separator = @"/";
+  NSLog(@"Note(SOGoMailManager): using '%@' as the IMAP4 folder separator.", 
+       imap4Separator);
+}
+
+- (id)initWithClient:(NGImap4Client *)_client password:(NSString *)_pwd {
+  if (_client == nil || _pwd == nil) {
+    [self release];
+    return nil;
+  }
+  
+  if ((self = [super init])) {
+    self->client   = [_client retain];
+    self->password = [_pwd    copy];
+    
+    self->creationTime = [[NSDate alloc] init];
+  }
+  return self;
+}
+- (id)init {
+  return [self initWithClient:nil password:nil];
+}
+
+- (void)dealloc {
+  [self->urlToRights     release];
+  [self->cachedUIDs      release];
+  [self->uidFolderURL    release];
+  [self->uidSortOrdering release];
+  [self->creationTime    release];
+  [self->subfolders      release];
+  [self->password        release];
+  [self->client          release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (NGImap4Client *)client {
+  return self->client;
+}
+- (BOOL)isValidPassword:(NSString *)_pwd {
+  return [self->password isEqualToString:_pwd];
+}
+
+- (NSDate *)creationTime {
+  return self->creationTime;
+}
+
+- (void)cacheHierarchyResults:(NSDictionary *)_hierarchy {
+  ASSIGNCOPY(self->subfolders, _hierarchy);
+}
+- (NSDictionary *)cachedHierarchyResults {
+  return self->subfolders;
+}
+- (void)flushFolderHierarchyCache {
+  [self->subfolders  release]; self->subfolders  = nil;
+  [self->urlToRights release]; self->urlToRights = nil;
+}
+
+/* rights */
+
+- (NSString *)cachedMyRightsForURL:(NSURL *)_url {
+  return (_url != nil) ? [self->urlToRights objectForKey:_url] : nil;
+}
+- (void)cacheMyRights:(NSString *)_rights forURL:(NSURL *)_url {
+  if (self->urlToRights == nil)
+    self->urlToRights = [[NSMutableDictionary alloc] initWithCapacity:8];
+  [self->urlToRights setObject:_rights forKey:_url];
+}
+
+/* UIDs */
+
+- (id)cachedUIDsForURL:(NSURL *)_url qualifier:(id)_q sortOrdering:(id)_so {
+  if (_q != nil)
+    return nil;
+  if (![_so isEqual:self->uidSortOrdering])
+    return nil;
+  if (![self->uidFolderURL isEqual:_url])
+    return nil;
+  
+  return self->cachedUIDs;
+}
+
+- (void)cacheUIDs:(NSArray *)_uids forURL:(NSURL *)_url
+  qualifier:(id)_q sortOrdering:(id)_so
+{
+  if (_q != nil)
+    return;
+
+  ASSIGNCOPY(self->uidSortOrdering, _so);
+  ASSIGNCOPY(self->uidFolderURL,    _url);
+  ASSIGNCOPY(self->cachedUIDs,      _uids);
+}
+
+- (void)flushMailCaches {
+  ASSIGN(self->uidSortOrdering, nil);
+  ASSIGN(self->uidFolderURL,    nil);
+  ASSIGN(self->cachedUIDs,      nil);
+}
+
+
+/* errors */
+
+- (NSException *)errorCouldNotSelectURL:(NSURL *)_url {
+  NSDictionary *ui;
+  NSString *r;
+  
+  r = [_url isNotNull]
+    ? [@"Could not select IMAP4 folder: " stringByAppendingString:
+         [_url absoluteString]]
+    : @"Could not select IMAP4 folder!";
+
+  ui = [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSNumber numberWithInt:404], @"http-status",
+                      _url, @"url",
+                    nil];
+  
+  return [NSException exceptionWithName:@"NGImap4Exception"
+                     reason:r userInfo:ui];
+}
+
+- (NSException *)errorForResult:(NSDictionary *)_result text:(NSString *)_txt {
+  NSDictionary *ui;
+  NSString *r;
+  int      status;
+  
+  if ([[_result valueForKey:@"result"] boolValue])
+    return nil; /* everything went fine! */
+  
+  if ((r = [_result valueForKey:@"reason"]) != nil)
+    r = [[_txt stringByAppendingString:@": "] stringByAppendingString:r];
+  else
+    r = _txt;
+  
+  if ([r isEqualToString:@"Permission denied"]) {
+    /* different for each server?, no error codes in IMAP4 ... */
+    status = 403 /* Forbidden */;
+  }
+  else
+    status = 500 /* internal server error */;
+  
+  ui = [NSDictionary dictionaryWithObjectsAndKeys:
+                      [NSNumber numberWithInt:status], @"http-status",
+                      _result, @"rawResult",
+                    nil];
+  
+  return [NSException exceptionWithName:@"NGImap4Exception"
+                     reason:r userInfo:ui];
+}
+
+/* IMAP4 path/url processing methods */
+
+NSArray *SOGoMailGetDirectChildren(NSArray *_array, NSString *_fn) {
+  /*
+    Scans string '_array' for strings which start with the string in '_fn'.
+    Then split on '/'.
+  */
+  NSMutableArray *ma;
+  unsigned i, count, prefixlen;
+  
+  if ((count = [_array count]) < 2)
+    /* one entry is the folder itself, so we need at least two */
+    return [NSArray array];
+  
+#if __APPLE__ 
+  // TODO: somehow results are different on OSX
+  prefixlen = [_fn isEqualToString:@""] ? 0 : [_fn length] + 1;
+#else
+  prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length] + 1;
+#endif
+  ma = [NSMutableArray arrayWithCapacity:count];
+  for (i = 0; i < count; i++) {
+    NSString *p;
+    
+    p = [_array objectAtIndex:i];
+    if ([p length] <= prefixlen)
+      continue;
+    if (prefixlen != 0 && ![p hasPrefix:_fn])
+      continue;
+    
+    /* cut of common part */
+    p = [p substringFromIndex:prefixlen];
+    
+    /* check whether the path is a sub-subfolder path */
+    if ([p rangeOfString:@"/"].length > 0)
+      continue;
+    
+    [ma addObject:p];
+  }
+  
+  [ma sortUsingSelector:@selector(compare:)];
+  return ma;
+}
+
+NSArray *SOGoMailExtractSubfolders(NSURL *_url, NSDictionary *_result) {
+  NSString     *folderName;
+  NSDictionary *result;
+  NSArray      *names;
+  NSArray      *flags;
+  
+  /* Note: the result is normalized, that is, it contains / as the separator */
+  folderName = [_url path];
+#if __APPLE__ 
+  /* normalized results already have the / in front on libFoundation?! */
+  if ([folderName hasPrefix:@"/"]) 
+    folderName = [folderName substringFromIndex:1];
+#endif
+  
+  result = [_result valueForKey:@"list"];
+  
+  /* Cyrus already tells us whether we need to check for children */
+  flags = [result objectForKey:folderName];
+  if ([flags containsObject:@"hasnochildren"]) {
+    if (debugKeys)
+      NSLog(@"%s: folder %@ has no children.", __PRETTY_FUNCTION__,folderName);
+    return nil;
+  }
+
+  if (debugKeys) {
+    NSLog(@"%s: all keys %@: %@", __PRETTY_FUNCTION__, folderName, 
+         [[result allKeys] componentsJoinedByString:@", "]);
+  }
+  
+  names = SOGoMailGetDirectChildren([result allKeys], folderName);
+  if (debugKeys) {
+    NSLog(@"%s: subfolders of '%@': %@", __PRETTY_FUNCTION__, folderName, 
+         [names componentsJoinedByString:@","]);
+  }
+  return names;
+}
+
+- (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(NSString *)_fn {
+  return SOGoMailGetDirectChildren(_array, _fn);
+}
+- (NSArray *)extractSubfoldersForURL:(NSURL *)_url
+  fromResultSet:(NSDictionary *)_result
+{
+  return SOGoMailExtractSubfolders(_url, _result);
+}
+
+- (NSString *)imap4Separator {
+  // TODO: make server specific ivar!
+  return imap4Separator;
+}
+
+- (NSString *)imap4FolderNameForURL:(NSURL *)_url removeFileName:(BOOL)_delfn {
+  /* a bit hackish, but should be OK */
+  NSString *folderName;
+  NSArray  *names;
+
+  if (_url == nil)
+    return nil;
+  
+  folderName = [_url path];
+  if ([folderName length] == 0)
+    return nil;
+  if ([folderName characterAtIndex:0] == '/')
+    folderName = [folderName substringFromIndex:1];
+  
+  if (_delfn) folderName = [folderName stringByDeletingLastPathComponent];
+  
+  if ([[self imap4Separator] isEqualToString:@"/"])
+    return folderName;
+  
+  names = [folderName pathComponents];
+  return [names componentsJoinedByString:[self imap4Separator]];
+}
+- (NSString *)imap4FolderNameForURL:(NSURL *)_url {
+  return [self imap4FolderNameForURL:_url removeFileName:NO];
+}
+
+- (NSArray *)extractFoldersFromResultSet:(NSDictionary *)_result {
+  /* Note: the result is normalized, that is, it contains / as the separator */
+  return [[_result valueForKey:@"list"] allKeys];
+}
+
+/* folder selections */
+
+- (BOOL)selectFolder:(id)_url {
+  NSDictionary *result;
+  NSString     *newFolder;
+  
+  newFolder = [_url isKindOfClass:[NSURL class]]
+    ? [self imap4FolderNameForURL:_url]
+    : _url;
+  
+  if (!alwaysSelect) {
+    if ([[[self client] selectedFolderName] isEqualToString:newFolder])
+      return YES;
+  }
+  
+  result = [[self client] select:newFolder];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"could not select URL: %@: %@", _url, result];
+    return NO;
+  }
+
+  return YES;
+}
+
+- (BOOL)isPermissionDeniedResult:(id)_result {
+  if ([[_result valueForKey:@"result"] intValue] != 0)
+    return NO;
+  
+  return [[_result valueForKey:@"reason"] 
+                  isEqualToString:@"Permission denied"];
+}
+
+/* folder operations */
+
+- (NSArray *)subfoldersForURL:(NSURL *)_url {
+  NSDictionary  *result;
+
+  /* check hierarchy cache */
+  
+  if ((result = [self cachedHierarchyResults]) != nil)
+    return [self extractSubfoldersForURL:_url fromResultSet:result];
+  
+  [self debugWithFormat:@"  no folders cached yet .."];
+  
+  /* fetch _all_ folders */
+  
+  result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*")
+                         pattern:@"*"];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"listing of folder failed!"];
+    return nil;
+  }
+  
+  /* cache results */
+  
+  if ([result isNotNull]) {
+    [self cacheHierarchyResults:result];
+    if (debugCache) {
+      [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", 
+             self, result, [result count]];
+    }
+  }
+  
+  /* extract list */
+  
+  return [self extractSubfoldersForURL:_url fromResultSet:result];
+}
+
+- (NSArray *)allFoldersForURL:(NSURL *)_url {
+  NSDictionary *result;
+  
+  /* check hierarchy cache */
+  
+  if ((result = [self cachedHierarchyResults]) != nil)
+    return [self extractFoldersFromResultSet:result];
+  
+  [self debugWithFormat:@"  no folders cached yet .."];
+  
+  /* fetch _all_ folders */
+  
+  result = [[self client] list:@"INBOX" pattern:@"*"];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self logWithFormat:@"ERROR: listing of folder failed!"];
+    return nil;
+  }
+  
+  /* cache results */
+  
+  if ([result isNotNull]) {
+    [self cacheHierarchyResults:result];
+    if (debugCache) {
+      [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", 
+             self, result, [result count]];
+    }
+  }
+  
+  /* extract list */
+  return [self extractFoldersFromResultSet:result];
+}
+
+/* message operations */
+
+- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier
+  sortOrdering:(id)_so
+{
+  /* 
+     sortOrdering can be an NSString, an EOSortOrdering or an array of EOS.
+  */
+  NSDictionary *result;
+  NSArray      *uids;
+
+  /* check cache */
+  
+  uids = [self cachedUIDsForURL:_url qualifier:_qualifier sortOrdering:_so];
+  if (uids != nil) {
+    if (debugCache) [self logWithFormat:@"reusing uid cache!"];
+    return [uids isNotNull] ? uids : nil;
+  }
+  
+  /* select folder and fetch */
+  
+  if (![self selectFolder:_url])
+    return nil;
+  
+  result = [[self client] sort:_so qualifier:_qualifier encoding:@"UTF-8"];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"could not sort contents of URL: %@", _url];
+    return nil;
+  }
+  
+  uids = [result valueForKey:@"sort"];
+  if (![uids isNotNull]) {
+    [self errorWithFormat:@"got no UIDs for URL: %@: %@", _url, result];
+    return nil;
+  }
+  
+  /* cache */
+  
+  [self cacheUIDs:uids forURL:_url qualifier:_qualifier sortOrdering:_so];
+  return uids;
+}
+
+- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
+  parts:(NSArray *)_parts
+{
+  // currently returns a dict?!
+  /*
+    Allowed fetch keys:
+      UID
+      BODY.PEEK[<section>]<<partial>>
+      BODY            [this is the bodystructure, supported]
+      BODYSTRUCTURE   [not supported yet!]
+      ENVELOPE        [this is a parsed header, but does not include type]
+      FLAGS
+      INTERNALDATE
+      RFC822
+      RFC822.HEADER
+      RFC822.SIZE
+      RFC822.TEXT
+  */
+  NSDictionary *result;
+  
+  if (_uids == nil)
+    return nil;
+  if ([_uids count] == 0)
+    return nil; // TODO: might break empty folders?! return a dict!
+  
+  /* select folder */
+
+  if (![self selectFolder:_url])
+    return nil;
+  
+  /* fetch parts */
+  
+  // TODO: split uids into batches, otherwise Cyrus will complain
+  //       => not really important because we batch before (in the sort)
+  //       if the list is too long, we get a:
+  //       "* BYE Fatal error: word too long"
+  
+  result = [[self client] fetchUids:_uids parts:_parts];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"could not fetch %d uids for url: %@",
+           [_uids count],_url];
+    return nil;
+  }
+  
+  //[self logWithFormat:@"RESULT: %@", result];
+  return (id)result;
+}
+
+- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts {
+  // currently returns a dict
+  NSDictionary *result;
+  NSString *uid;
+  
+  if (![_url isNotNull]) return nil;
+  
+  /* select folder */
+
+  uid = [self imap4FolderNameForURL:_url removeFileName:YES];
+  if (![self selectFolder:uid])
+    return nil;
+  
+  /* fetch parts */
+  
+  uid = [[_url path] lastPathComponent];
+  
+  result = [client fetchUids:[NSArray arrayWithObject:uid] parts:_parts];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"could not fetch url: %@", _url];
+    return nil;
+  }
+  //[self logWithFormat:@"RESULT: %@", result];
+  return (id)result;
+}
+
+- (NSData *)fetchContentOfBodyPart:(NSString *)_partId atURL:(NSURL *)_url {
+  NSString *key;
+  NSArray  *parts;
+  id result, fetch, body;
+  
+  if (_partId == nil) return nil;
+  
+  key   = [@"body[" stringByAppendingString:_partId];
+  key   = [key stringByAppendingString:@"]"];
+  parts = [NSArray arrayWithObjects:&key count:1];
+  
+  /* fetch */
+  
+  result = [self fetchURL:_url parts:parts];
+  
+  /* process results */
+  
+  result = [result objectForKey:@"fetch"];
+  if ([result count] == 0) { /* did not find part */
+    [self errorWithFormat:@"did not find part: %@", _partId];
+    return nil;
+  }
+  
+  fetch = [result objectAtIndex:0];
+  if ((body = [fetch objectForKey:@"body"]) == nil) {
+    [self errorWithFormat:@"did not find body in response: %@", result];
+    return nil;
+  }
+  
+  if ((result = [body objectForKey:@"data"]) == nil) {
+    [self errorWithFormat:@"did not find data in body: %@", fetch];
+    return nil;
+  }
+  return result;
+}
+
+/* message flags */
+
+- (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f toURL:(NSURL *)_url {
+  id result;
+  
+  if (![_url isNotNull]) return nil;
+  if (![_f   isNotNull]) return nil;
+  
+  if (![_f isKindOfClass:[NSArray class]])
+    _f = [NSArray arrayWithObjects:&_f count:1];
+  
+  /* select folder */
+  
+  result = [self imap4FolderNameForURL:_url removeFileName:YES];
+  if (![self selectFolder:result])
+    return [self errorCouldNotSelectURL:_url];
+  
+  /* store flags */
+  
+  result = [[self client] storeUid:[[[_url path] lastPathComponent] intValue]
+                         add:[NSNumber numberWithBool:_flag]
+                         flags:_f];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [self errorForResult:result 
+                text:@"Failed to change flags of IMAP4 message"];
+  }
+  /* result contains 'fetch' key with the current flags */
+  return nil;
+}
+- (NSException *)addFlags:(id)_f toURL:(NSURL *)_u {
+  return [self addOrRemove:YES flags:_f toURL:_u];
+}
+- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u {
+  return [self addOrRemove:NO flags:_f toURL:_u];
+}
+
+- (NSException *)markURLDeleted:(NSURL *)_url {
+  return [self addOrRemove:YES flags:@"Deleted" toURL:_url];
+}
+
+- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url {
+  id result;
+  
+  if (![_url isNotNull]) return nil;
+  if (![_f   isNotNull]) return nil;
+
+  if (![_f isKindOfClass:[NSArray class]])
+    _f = [NSArray arrayWithObjects:&_f count:1];
+  
+  /* select folder */
+  
+  if (![self selectFolder:[self imap4FolderNameForURL:_url]])
+    return [self errorCouldNotSelectURL:_url];
+  
+  /* fetch all sequence numbers */
+  
+  result = [client searchWithQualifier:nil /* means: ALL */];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [self errorForResult:result 
+                text:@"Could not search in IMAP4 folder"];
+  }
+  
+  result = [result valueForKey:@"search"];
+  if ([result count] == 0) /* no messages in there, nothin' to be done */
+    return nil;
+  
+  /* store flags */
+  
+  result = [[self client] storeFlags:_f forMSNs:result addOrRemove:YES];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [self errorForResult:result 
+                text:@"Failed to change flags of IMAP4 message"];
+  }
+  
+  return nil;
+}
+
+/* posting new data */
+
+- (NSException *)postData:(NSData *)_data flags:(id)_f
+  toFolderURL:(NSURL *)_url
+{
+  id result;
+  
+  if (![_url isNotNull]) return nil;
+  if (![_f   isNotNull]) _f = [NSArray array];
+  
+  if (![_f isKindOfClass:[NSArray class]])
+    _f = [NSArray arrayWithObjects:&_f count:1];
+  
+  result = [[self client] append:_data 
+                         toFolder:[self imap4FolderNameForURL:_url]
+                         withFlags:_f];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorForResult:result text:@"Failed to store message"];
+  
+  /* result contains 'fetch' key with the current flags */
+  
+  // TODO: need to flush any caches?
+  return nil;
+}
+
+/* operations */
+
+- (NSException *)expungeAtURL:(NSURL *)_url {
+  NSString *p;
+  id result;
+  
+  /* select folder */
+  
+  p = [self imap4FolderNameForURL:_url removeFileName:NO];
+  if (![self selectFolder:p])
+    return [self errorCouldNotSelectURL:_url];
+  
+  /* expunge */
+  
+  result = [[self client] expunge];
+
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:@"could not expunge url: %@", _url];
+    return nil;
+  }
+  //[self logWithFormat:@"RESULT: %@", result];
+  return nil;
+}
+
+/* copying and moving */
+
+- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl {
+  NSString *srcname, *destname;
+  unsigned uid;
+  id result;
+  
+  /* names */
+  
+  srcname  = [self imap4FolderNameForURL:_srcurl removeFileName:YES];
+  uid      = [[[_srcurl path] lastPathComponent] unsignedIntValue];
+  destname = [self imap4FolderNameForURL:_desturl];
+  
+  /* select source folder */
+  
+  if (![self selectFolder:srcname])
+    return [self errorCouldNotSelectURL:_srcurl];
+  
+  /* copy */
+  
+  result = [[self client] copyUid:uid toFolder:destname];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorForResult:result text:@"Copy operation failed"];
+  
+  // TODO: need to flush some caches?
+  
+  return nil;
+}
+
+/* managing folders */
+
+- (BOOL)doesMailboxExistAtURL:(NSURL *)_url {
+  NSString *folderName;
+  id result;
+  
+  /* check in hierarchy cache */
+  
+  if ((result = [self cachedHierarchyResults]) != nil) {
+    result = [result objectForKey:@"list"];
+    return ([result objectForKey:[_url path]] != nil) ? YES : NO;
+  }
+  
+  /* check using IMAP4 select */
+  // TODO: we should probably just fetch the whole hierarchy?
+  
+  folderName = [self imap4FolderNameForURL:_url];
+  result = [[self client] select:folderName];
+  if (![[result valueForKey:@"result"] boolValue])
+    return NO;
+  
+  return YES;
+}
+
+- (id)infoForMailboxAtURL:(NSURL *)_url {
+  NGImap4MailboxInfo *info;
+  NSString        *folderName;
+  id result;
+
+  folderName = [self imap4FolderNameForURL:_url];
+  result     = [[self client] select:folderName];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorCouldNotSelectURL:_url];
+  
+  info = [[NGImap4MailboxInfo alloc] initWithURL:_url folderName:folderName
+                                    selectDictionary:result];
+  return [info autorelease];
+}
+
+- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url {
+  NSString *newPath;
+  id       result;
+  
+  /* construct path */
+  
+  newPath = [self imap4FolderNameForURL:_url];
+  newPath = [newPath stringByAppendingString:[self imap4Separator]];
+  newPath = [newPath stringByAppendingString:_mailbox];
+  
+  /* create */
+  
+  result = [[self client] create:newPath];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorForResult:result text:@"Failed to create folder"];
+  
+  [self flushFolderHierarchyCache];
+  // [self debugWithFormat:@"created mailbox: %@: %@", newPath, result];
+  return nil;
+}
+
+- (NSException *)deleteMailboxAtURL:(NSURL *)_url {
+  NSString *path;
+  id       result;
+
+  /* delete */
+  
+  path   = [self imap4FolderNameForURL:_url];
+  result = [[self client] delete:path];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorForResult:result text:@"Failed to delete folder"];
+  
+  [self flushFolderHierarchyCache];
+#if 0
+  [self debugWithFormat:@"delete mailbox %@: %@", _url, result];
+#endif
+  return nil;
+}
+
+- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl {
+  NSString *srcname, *destname;
+  id result;
+  
+  /* rename */
+  
+  srcname  = [self imap4FolderNameForURL:_srcurl];
+  destname = [self imap4FolderNameForURL:_desturl];
+  
+  result = [[self client] rename:srcname to:destname];
+  if (![[result valueForKey:@"result"] boolValue])
+    return [self errorForResult:result text:@"Failed to move folder"];
+  
+  [self flushFolderHierarchyCache];
+#if 0
+  [self debugWithFormat:@"renamed mailbox %@: %@", _srcurl, result];
+#endif
+  return nil;
+}
+
+/* ACLs */
+
+- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url {
+  /*
+    Returns a mapping of uid => permission strings, eg:
+      guizmo.g = lrs;
+      root     = lrswipcda;
+  */
+  NSString *folderName;
+  id       result;
+  
+  folderName = [self imap4FolderNameForURL:_url];
+  result     = [[self client] getACL:folderName];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return (id)[self errorForResult:result
+                    text:@"Failed to get ACL of folder"];
+  }
+  
+  return [result valueForKey:@"acl"];
+}
+
+- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url {
+  NSString *folderName;
+  id       result;
+  
+  /* check cache */
+  
+  if ((result = [self cachedMyRightsForURL:_url]) != nil)
+    return result;
+
+  /* run IMAP4 op */
+  
+  folderName = [self imap4FolderNameForURL:_url];
+  result     = [[self client] myRights:folderName];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return (id)[self errorForResult:result 
+                    text:@"Failed to get myrights on folder"];
+  }
+  
+  /* cache results */
+  
+  if ((result = [result valueForKey:@"myrights"]) != nil)
+    [self cacheMyRights:result forURL:_url];
+  return result;
+}
+
+@end /* NGImap4Connection */
diff --git a/sope-mime/NGImap4/NGImap4ConnectionManager.h b/sope-mime/NGImap4/NGImap4ConnectionManager.h
new file mode 100644 (file)
index 0000000..804a421
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __NGImap4ConnectionManager_H__
+#define __NGImap4ConnectionManager_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  NGImap4ConnectionManager
+  
+  This class manages and pools NGImap4Connection objects.
+*/
+
+@class NSString, NSTimer, NSMutableDictionary, NSURL;
+@class NGImap4Connection, NGImap4Client;
+
+@interface NGImap4ConnectionManager : NSObject
+{
+  NSMutableDictionary *urlToEntry;
+  NSTimer *gcTimer;
+}
+
++ (id)defaultConnectionManager;
+
+/* client object */
+
+- (NGImap4Connection *)connectionForURL:(NSURL *)_url password:(NSString *)_p;
+
+- (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd;
+
+- (void)flushCachesForURL:(NSURL *)_url;
+
+@end
+
+#endif /* __NGImap4ConnectionManager_H__ */
diff --git a/sope-mime/NGImap4/NGImap4ConnectionManager.m b/sope-mime/NGImap4/NGImap4ConnectionManager.m
new file mode 100644 (file)
index 0000000..f25511e
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "NGImap4ConnectionManager.h"
+#include "NGImap4Connection.h"
+#include "NGImap4Client.h"
+#include "imCommon.h"
+
+@implementation NGImap4ConnectionManager
+
+static BOOL           debugOn    = NO;
+static BOOL           debugCache = NO;
+static BOOL           poolingOff = NO;
+static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */;
+
++ (void)initialize {
+  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
+  
+  debugOn      = [ud boolForKey:@"NGImap4EnableIMAP4Debug"];
+  debugCache   = [ud boolForKey:@"NGImap4EnableIMAP4CacheDebug"];
+  poolingOff   = [ud boolForKey:@"NGImap4DisableIMAP4Pooling"];
+  
+  if (debugOn)    NSLog(@"Note: NGImap4EnableIMAP4Debug is enabled!");
+  if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!");
+}
+
++ (id)defaultConnectionManager {
+  static NGImap4ConnectionManager *manager = nil; // THREAD
+  if (manager == nil) 
+    manager = [[self alloc] init];
+  return manager;
+}
+
+- (id)init {
+  if ((self = [super init])) {
+    if (!poolingOff) {
+      self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256];
+    }
+    
+    self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
+                               PoolScanInterval
+                             target:self selector:@selector(_garbageCollect:)
+                             userInfo:nil repeats:YES] retain];
+  }
+  return self;
+}
+
+- (void)dealloc {
+  if (self->gcTimer) [self->gcTimer invalidate];
+  [self->urlToEntry release];
+  [self->gcTimer    release];
+  [super dealloc];
+}
+
+/* cache */
+
+- (id)cacheKeyForURL:(NSURL *)_url {
+  // protocol, user, host, port
+  return [NSString stringWithFormat:@"%@://%@@%@:%@",
+                  [_url scheme], [_url user], [_url host], [_url port]];
+}
+
+- (NGImap4Connection *)entryForURL:(NSURL *)_url {
+  if (_url == nil)
+    return nil;
+  
+  return [self->urlToEntry objectForKey:[self cacheKeyForURL:_url]];
+}
+- (void)cacheEntry:(NGImap4Connection *)_entry forURL:(NSURL *)_url {
+  if (_entry == nil) _entry = (id)[NSNull null];
+  [self->urlToEntry setObject:_entry forKey:[self cacheKeyForURL:_url]];
+}
+
+- (void)_garbageCollect:(NSTimer *)_timer {
+  // TODO: scan for old IMAP4 channels
+  [self debugWithFormat:@"should collect IMAP4 channels (%d active)",
+         [self->urlToEntry count]];
+}
+
+- (NGImap4Connection *)connectionForURL:(NSURL *)_url password:(NSString *)_p {
+  /*
+    Three cases:
+    a) not yet connected             => create new entry and connect
+    b) connected, correct password   => return cached entry
+    c) connected, different password => try to recreate entry
+  */
+  NGImap4Connection *entry;
+  NGImap4Client *client;
+
+  /* check cache */
+  
+  if ((entry = [self entryForURL:_url]) != nil) {
+    if ([entry isValidPassword:_p]) {
+      if (debugCache)
+       [self logWithFormat:@"valid password, reusing cache entry ..."];
+      return entry;
+    }
+    
+    /* different password, password could have changed! */
+    if (debugCache)
+      [self logWithFormat:@"different password than cached entry: %@", _url];
+    entry = nil;
+  }
+  else
+    [self debugWithFormat:@"no connection cached yet for url: %@", _url];
+  
+  /* try to login */
+  
+  client = [entry isValidPassword:_p]
+    ? [entry client]
+    : [self imap4ClientForURL:_url password:_p];
+  
+  if (client == nil)
+    return nil;
+  
+  /* sideeffect of -imap4ClientForURL:password: is to create a cache entry */
+  return [self entryForURL:_url];
+}
+
+/* client object */
+
+- (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
+  // TODO: move to some global IMAP4 connection pool manager
+  NGImap4Connection *entry;
+  NGImap4Client *client;
+  NSDictionary  *result;
+  
+  if (_url == nil)
+    return nil;
+
+  /* check connection pool */
+  
+  if ((entry = [self entryForURL:_url]) != nil) {
+    if ([entry isValidPassword:_pwd]) {
+      [self debugWithFormat:@"reused IMAP4 connection for URL: %@", _url];
+      return [entry client];
+    }
+    
+    /* different password, password could have changed! */
+    entry = nil;
+  }
+  
+  /* setup connection and attempt login */
+  
+  if ((client = [NGImap4Client clientWithURL:_url]) == nil)
+    return nil;
+  
+  result = [client login:[_url user] password:_pwd];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    [self errorWithFormat:
+            @"IMAP4 login failed:\n"
+           @"  host=%@, user=%@, pwd=%s\n"
+           @"  url=%@\n  base=%@\n  base-class=%@)\n"
+           @"  = %@", 
+            [_url host], [_url user], [_pwd length] > 0 ? "yes" : "no", 
+           [_url absoluteString],
+           [_url baseURL],
+            NSStringFromClass([[_url baseURL] class]),
+            client];
+    return nil;
+  }
+  
+  [self debugWithFormat:@"created new IMAP4 connection for URL: %@", _url];
+  
+  /* cache connection in pool */
+  
+  entry = [[NGImap4Connection alloc] initWithClient:client 
+                                          password:_pwd];
+  [self cacheEntry:entry forURL:_url];
+  [entry release]; entry = nil;
+  
+  return client;
+}
+
+- (void)flushCachesForURL:(NSURL *)_url {
+  NGImap4Connection *entry;
+  
+  if ((entry = [self entryForURL:_url]) == nil) /* nothing cached */
+    return;
+  
+  [entry flushFolderHierarchyCache];
+  [entry flushMailCaches];
+}
+
+/* debugging */
+
+- (BOOL)isDebuggingEnabled {
+  return debugOn;
+}
+
+@end /* NGImap4ConnectionManager */
diff --git a/sope-mime/NGImap4/NGImap4MailboxInfo.h b/sope-mime/NGImap4/NGImap4MailboxInfo.h
new file mode 100644 (file)
index 0000000..5c131dc
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+  Copyright (C) 2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __NGImap4MailboxInfo_H__
+#define __NGImap4MailboxInfo_H__
+
+#import <Foundation/NSObject.h>
+
+/*
+  NGImap4MailboxInfo
+  
+  Represents the info returned by an IMAP4 select. Use NGImap4Connection to
+  retrieve the data.
+*/
+
+@class NSString, NSDate, NSArray, NSURL, NSDictionary;
+
+@interface NGImap4MailboxInfo : NSObject
+{
+  NSDate   *timestamp;
+  NSURL    *url;
+  NSString *name;
+  NSArray  *allowedFlags;
+  NSString *access;
+  unsigned int recent;
+}
+
+- (id)initWithURL:(NSURL *)_url folderName:(NSString *)_name
+  selectDictionary:(NSDictionary *)_dict;
+
+/* accessors */
+
+- (NSDate *)timestamp;
+- (NSURL *)url;
+- (NSString *)name;
+- (NSArray *)allowedFlags;
+- (NSString *)access;
+- (unsigned int)recent;
+
+@end
+
+#endif /* __NGImap4MailboxInfo_H__ */
diff --git a/sope-mime/NGImap4/NGImap4MailboxInfo.m b/sope-mime/NGImap4/NGImap4MailboxInfo.m
new file mode 100644 (file)
index 0000000..6159c73
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+  Copyright (C) 2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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, or (at your option) any
+  later version.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include "NGImap4MailboxInfo.h"
+#include "imCommon.h"
+
+@implementation NGImap4MailboxInfo
+
+- (id)initWithURL:(NSURL *)_url folderName:(NSString *)_name
+  selectDictionary:(NSDictionary *)_dict
+{
+  if (_dict == nil || (_url == nil && _name == nil)) {
+    [self release];
+    return nil;
+  }
+  
+  if ((self = [super init])) {
+    self->timestamp    = [[NSDate alloc] init];
+    self->url          = [_url  copy];
+    self->name         = [_name copy];
+    self->allowedFlags = [[_dict objectForKey:@"flags"]  copy];
+    self->access       = [[_dict objectForKey:@"access"] copy];
+    self->recent       = [[_dict objectForKey:@"recent"] unsignedIntValue];
+  }
+  return self;
+}
+- (id)init {
+  return [self initWithURL:nil folderName: nil selectDictionary:nil];
+}
+
+- (void)dealloc {
+  [self->timestamp    release];
+  [self->url          release];
+  [self->name         release];
+  [self->allowedFlags release];
+  [self->access       release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (NSDate *)timestamp {
+  return self->timestamp;
+}
+- (NSURL *)url {
+  return self->url;
+}
+- (NSString *)name {
+  return self->name;
+}
+- (NSArray *)allowedFlags {
+  return self->allowedFlags;
+}
+- (NSString *)access {
+  return self->access;
+}
+- (unsigned int)recent {
+  return self->recent;
+}
+
+/* description */
+
+- (void)appendAttributesToDescription:(NSMutableString *)_ms {
+  if (self->name)   [_ms appendFormat:@" name=%@",  self->name];
+  if (self->access) [_ms appendFormat:@" access=%@",  self->access];
+  
+  if (self->recent != 0) [_ms appendFormat:@" recent=%d", self->recent];
+
+  [_ms appendFormat:@" flags=%@", 
+       [[self allowedFlags] componentsJoinedByString:@","]];
+}
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:64];
+  [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
+  [self appendAttributesToDescription:ms];
+  [ms appendString:@">"];
+  return ms;
+}
+
+@end /* NGImap4MailboxInfo */
index 31c2fe4f5bff3f5ab484ceb388b50e7cdd08b966..bd3b377ce6bef7c6f359422df208a803c315de8a 100644 (file)
@@ -2,7 +2,7 @@
 
 MAJOR_VERSION:=4
 MINOR_VERSION:=5
-SUBMINOR_VERSION:=222
+SUBMINOR_VERSION:=223
 
 # v4.5.214 requires libNGExtensions v4.5.146
 # v4.2.149 requires libNGStreams    v4.2.34