]> err.no Git - scalable-opengroupware.org/commitdiff
refactoring in mailer
authorhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 11 Jul 2005 10:32:44 +0000 (10:32 +0000)
committerhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Mon, 11 Jul 2005 10:32:44 +0000 (10:32 +0000)
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@708 d1b88da0-ebda-0310-925b-ed51d893ca5b

SOGo/SoObjects/Mailer/ChangeLog
SOGo/SoObjects/Mailer/SOGoMailBaseObject.h
SOGo/SoObjects/Mailer/SOGoMailBaseObject.m
SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.h
SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.m
SOGo/SoObjects/Mailer/SOGoMailManager.h
SOGo/SoObjects/Mailer/SOGoMailManager.m
SOGo/SoObjects/Mailer/Version

index a3eb41a0478279ec7eda6eab7180938cadcb9301..a26ab25b98aa636db8984c70b02c17dee2ef4c39 100644 (file)
@@ -1,3 +1,12 @@
+2005-07-11  Helge Hess  <helge.hess@opengroupware.org>
+
+       * v0.9.95
+
+       * SOGoMailBaseObject.m: removed -imapFolderName method
+
+       * SOGoMailManager.m, SOGoMailConnectionEntry.m: moved implementations
+         of operations to SOGoMailConnectionEntry
+
 2005-07-08  Helge Hess  <helge.hess@opengroupware.org>
 
        * SOGoMailAccounts.m: use WOContext method from libSOGo to detect
index 77951f9cd2db363ef176d00e67fe84f254d12cc7..da6b83c9d19aa203a05d659909ee657a49b15a37 100644 (file)
@@ -60,7 +60,6 @@
 - (NSURL *)imap4URL;
 - (NSString *)imap4Login;
 - (NSString *)imap4Password;
-- (NSString *)imap4FolderName;
 - (NGImap4Client *)imap4Client;
 
 - (void)flushMailCaches;
index 07c4537148b743b714a0924d88018210da9b5897..d366f40e59fca37ae8ca912ce9902bd5c7dfab15 100644 (file)
@@ -101,10 +101,6 @@ static BOOL debugOn = YES;
   return self->imap4URL;
 }
 
-- (NSString *)imap4FolderName {
-  return [[self mailManager] imap4FolderNameForURL:[self imap4URL]];
-}
-
 - (NSString *)imap4Login {
   if (![[self container] respondsToSelector:_cmd])
     return nil;
index 0e3fcfc2e6e549cc3d30a8e8b30004dbae4dcd33..962df8b324f78cbf87849f78d3d179b1cda076db 100644 (file)
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2004 SKYRIX Software AG
+  Copyright (C) 2004-2005 SKYRIX Software AG
 
   This file is part of OpenGroupware.org.
 
@@ -37,6 +37,7 @@
 */
 
 @class NSString, NSDate, NSArray, NSDictionary, NSURL, NSMutableDictionary;
+@class NSException, NSData;
 @class NGImap4Client;
 
 @interface SOGoMailConnectionEntry : NSObject
 
 - (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
 
+extern NSArray *SOGoMailGetDirectChildren(NSArray *_array, NSString *_fn);
+extern NSArray *SOGoMailExtractSubfolders(NSURL *_url, NSDictionary *_result);
+
 #endif /* __SOGo_SOGoMailConnectionEntry_H__ */
index a113fda7e0fe5590fdf16fd2d55ea64e9bd001df..bfb7a166e0a9a43bb67457801b23eed6a809edb8 100644 (file)
@@ -1,5 +1,5 @@
 /*
-  Copyright (C) 2004 SKYRIX Software AG
+  Copyright (C) 2004-2005 SKYRIX Software AG
 
   This file is part of OpenGroupware.org.
 
 */
 
 #include "SOGoMailConnectionEntry.h"
+#include "SOGoMailboxInfo.h"
 #include "common.h"
 
 @implementation SOGoMailConnectionEntry
 
+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];
   ASSIGN(self->cachedUIDs,      nil);
 }
 
+/* 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 */
+  
+  if (![self selectFolder:
+              [self imap4FolderNameForURL:_url removeFileName:YES]]) {
+    return [NSException exceptionWithHTTPStatus:404 /* server error */
+                       reason:@"could not select IMAP4 folder"];
+  }
+  
+  /* store flags */
+  
+  result = [[self client] storeUid:[[[_url path] lastPathComponent] intValue]
+                         add:[NSNumber numberWithBool:_flag]
+                         flags:_f];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    unsigned int status;
+    NSString *r;
+    
+    r = [result valueForKey:@"reason"];
+    if ([r isEqualToString:@"Permission denied"]) {
+      /* different for each server?, no error codes in IMAP4 ... */
+      status = 403 /* Forbidden */;
+    }
+    else
+      status = 500 /* internal server error */;
+    
+    [self logWithFormat:@"DEBUG: fail result %@", result];
+
+    r = [@"Failed to add flag to IMAP4 message: " stringByAppendingString:r];
+    
+    return [NSException exceptionWithHTTPStatus:status /* server error */
+                       reason:r];
+  }
+  /* 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 [NSException exceptionWithHTTPStatus:404 /* not found */
+                       reason:@"could not select IMAP4 folder"];
+  }
+  
+  /* fetch all sequence numbers */
+  
+  result = [client searchWithQualifier:nil /* means: ALL */];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:@"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 [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:@"could not change flags in IMAP4 folder"];
+  }
+  
+  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]) {
+    [self logWithFormat:@"DEBUG: fail result %@", result];
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:@"failed to store message to IMAP4 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 [NSException exceptionWithHTTPStatus:501 /* Server Error */
+                       reason:@"could not select IMAP4 folder!"];
+  }
+  
+  /* 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 [NSException exceptionWithHTTPStatus:404 /* Not Found */
+                       reason:@"did not find source folder"];
+  }
+  
+  /* copy */
+  
+  result = [[self client] copyUid:uid toFolder:destname];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [NSException exceptionWithHTTPStatus:500 /* Server Error */
+                       reason:@"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 {
+  SOGoMailboxInfo *info;
+  NSString        *folderName;
+  id result;
+
+  folderName = [self imap4FolderNameForURL:_url];
+  result     = [[self client] select:folderName];
+  if (![[result valueForKey:@"result"] boolValue]) {
+    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
+                       reason:@"did not find IMAP4 folder (select failed)"];
+  }
+  
+  info = [[SOGoMailboxInfo 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 ([self isPermissionDeniedResult:result]) {
+    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
+                       reason:@"creation of folders not allowed"];
+  }
+  else if ([[result valueForKey:@"result"] intValue] == 0) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:[result valueForKey:@"reason"]];
+  }
+  
+  [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 ([self isPermissionDeniedResult:result]) {
+    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
+                       reason:@"creation of folders not allowed"];
+  }
+  else if ([[result valueForKey:@"result"] intValue] == 0) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:[result valueForKey:@"reason"]];
+  }
+  
+  [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 ([self isPermissionDeniedResult:result]) {
+    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
+                       reason:@"creation of folders not allowed"];
+  }
+  else if ([[result valueForKey:@"result"] intValue] == 0) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:[result valueForKey:@"reason"]];
+  }
+  
+  [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]) {
+    [self logWithFormat:@"ERROR: getacl failed: %@", result];
+    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
+                       reason:@"did not find ACL for IMAP4 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]) {
+    [self logWithFormat:@"ERROR: myrights failed: %@", result];
+    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
+                       reason:@"did not find myrights for IMAP4 folder"];
+  }
+
+  /* cache results */
+  
+  if ((result = [result valueForKey:@"myrights"]) != nil)
+    [self cacheMyRights:result forURL:_url];
+  return result;
+}
+
 @end /* SOGoMailConnectionEntry */
index e7931c8990136fa26d3bc8be584c3df6051bf011..7cd9f7ed7daf8386aa3bf450b42a90dd02e783ea 100644 (file)
@@ -50,8 +50,6 @@
 
 /* folder hierarchy */
 
-- (NSString *)imap4Separator;
-- (NSString *)imap4FolderNameForURL:(NSURL *)_url;
 - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd;
 - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd;
 
index ea9e1fcf38b0b266a14273f441685c9d8222a916..0049d6bdaf61bff5bd256846ba46a21ff2e990bf 100644 (file)
@@ -21,7 +21,6 @@
 
 #include "SOGoMailManager.h"
 #include "SOGoMailConnectionEntry.h"
-#include "SOGoMailboxInfo.h"
 #include "common.h"
 
 /*
@@ -41,10 +40,7 @@ static BOOL           debugOn    = NO;
 static BOOL           debugCache = NO;
 static BOOL           debugKeys  = NO;
 static BOOL           poolingOff = NO;
-static BOOL           alwaysSelect     = NO;
-static BOOL           onlyFetchInbox   = NO;
 static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */;
-static NSString       *imap4Separator  = nil;
 
 + (void)initialize {
   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
@@ -52,19 +48,9 @@ static NSString       *imap4Separator  = nil;
   debugOn      = [ud boolForKey:@"SOGoEnableIMAP4Debug"];
   debugCache   = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"];
   poolingOff   = [ud boolForKey:@"SOGoDisableIMAP4Pooling"];
-  alwaysSelect = [ud boolForKey:@"SOGoAlwaysSelectIMAP4Folder"];
   
   if (debugOn)    NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!");
   if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!");
-
-  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)defaultMailManager {
@@ -161,6 +147,12 @@ static NSString       *imap4Separator  = nil;
   return [self entryForURL:_url];
 }
 
+- (NSException *)errorForMissingEntryAtURL:(NSURL *)_url {
+  // TODO: improve
+  return [NSException exceptionWithHTTPStatus:404 /* Not Found */
+                     reason:@"Did not find mail URL"];
+}
+
 /* client object */
 
 - (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
@@ -226,149 +218,10 @@ static NSString       *imap4Separator  = nil;
   [entry flushMailCaches];
 }
 
-- (BOOL)selectFolder:(id)_url inClient:(NGImap4Client *)_client {
-  NSDictionary *result;
-  NSString     *newFolder;
-  
-  newFolder = [_url isKindOfClass:[NSURL class]]
-    ? [self imap4FolderNameForURL:_url]
-    : _url;
-  
-  if (!alwaysSelect) {
-    if ([[_client selectedFolderName] isEqualToString:newFolder])
-      return YES;
-  }
-  
-  result = [_client select:newFolder];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self errorWithFormat:@"could not select URL: %@: %@", _url, result];
-    return NO;
-  }
-
-  return YES;
-}
-
 /* folder hierarchy */
 
-- (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(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;
-}
-
-- (NSString *)imap4Separator {
-  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 *)extractSubfoldersForURL:(NSURL *)_url
-  fromResultSet:(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)
-      [self logWithFormat:@"folder %@ has no children.", folderName];
-    return nil;
-  }
-
-  if (debugKeys) {
-    [self logWithFormat:@"all keys %@: %@", folderName, 
-         [[result allKeys] componentsJoinedByString:@", "]];
-  }
-  
-  names = [self _getDirectChildren:[result allKeys] folderName:folderName];
-  if (debugKeys) {
-    [self logWithFormat:@"subfolders of '%@': %@", folderName, 
-           [names componentsJoinedByString:@","]];
-  }
-  return names;
-}
-
-- (NSArray *)extractFoldersFromResultSet:(NSDictionary *)_result {
-  /* Note: the result is normalized, that is, it contains / as the separator */
-  return [[_result valueForKey:@"list"] allKeys];
-}
-
 - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  NSDictionary  *result;
 
   if (debugKeys)
     [self debugWithFormat:@"subfolders for URL: %@ ...",[_url absoluteString]];
@@ -378,44 +231,12 @@ static NSString       *imap4Separator  = nil;
   if ((entry = [self entryForURL:_url password:_pwd]) == nil)
     return nil;
   
-  /* check hierarchy cache */
-  
-  if ((result = [entry cachedHierarchyResults]) != nil)
-    return [self extractSubfoldersForURL:_url fromResultSet:result];
-  
-  [self debugWithFormat:@"  no folders cached yet .."];
-  
-  /* fetch _all_ folders */
-  
-  result = [[entry client] list:(onlyFetchInbox ? @"INBOX" : @"*")
-                          pattern:@"*"];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self errorWithFormat:@"listing of folder failed!"];
-    return nil;
-  }
-  
-  /* cache results */
-  
-  if ([result isNotNull]) {
-    if (entry == nil) /* required in case the entry was not setup */
-      entry = [self entryForURL:_url];
-    
-    [entry cacheHierarchyResults:result];
-    if (debugCache) {
-      [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", 
-             entry, result, [result count]];
-    }
-  }
-  
-  /* extract list */
-  
-  return [self extractSubfoldersForURL:_url fromResultSet:result];
+  return [entry subfoldersForURL:_url];
 }
 
 - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  NSDictionary  *result;
-
+  
   if (debugKeys)
     [self debugWithFormat:@"folders for URL: %@ ...",[_url absoluteString]];
   
@@ -424,36 +245,7 @@ static NSString       *imap4Separator  = nil;
   if ((entry = [self entryForURL:_url password:_pwd]) == nil)
     return nil;
   
-  /* check hierarchy cache */
-  
-  if ((result = [entry cachedHierarchyResults]) != nil)
-    return [self extractFoldersFromResultSet:result];
-  
-  [self debugWithFormat:@"  no folders cached yet .."];
-  
-  /* fetch _all_ folders */
-  
-  result = [[entry client] list:@"INBOX" pattern:@"*"];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self logWithFormat:@"ERROR: listing of folder failed!"];
-    return nil;
-  }
-  
-  /* cache results */
-  
-  if ([result isNotNull]) {
-    if (entry == nil) /* required in case the entry was not setup */
-      entry = [self entryForURL:_url];
-    
-    [entry cacheHierarchyResults:result];
-    if (debugCache) {
-      [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", 
-             entry, result, [result count]];
-    }
-  }
-  
-  /* extract list */
-  return [self extractFoldersFromResultSet:result];
+  return [entry allFoldersForURL:_url];
 }
 
 /* messages */
@@ -465,43 +257,12 @@ static NSString       *imap4Separator  = nil;
      sortOrdering can be an NSString, an EOSortOrdering or an array of EOS.
   */
   SOGoMailConnectionEntry *entry;
-  NSDictionary  *result;
-  NSArray       *uids;
   
   /* check connection cache */
-  
   if ((entry = [self entryForURL:_url password:_pwd]) == nil)
     return nil;
   
-  /* check cache */
-  
-  uids = [entry 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 inClient:[entry client]])
-    return nil;
-  
-  result = [[entry 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 */
-  
-  [entry cacheUIDs:uids forURL:_url qualifier:_qualifier sortOrdering:_so];
-  return uids;
+  return [entry fetchUIDsInURL:_url qualifier:_qualifier sortOrdering:_so];
 }
 
 - (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
@@ -522,187 +283,59 @@ static NSString       *imap4Separator  = nil;
       RFC822.SIZE
       RFC822.TEXT
   */
-  NGImap4Client *client;
-  NSDictionary  *result;
+  SOGoMailConnectionEntry *entry;
   
   if (_uids == nil)
     return nil;
   if ([_uids count] == 0)
     return nil; // TODO: might break empty folders?! return a dict!
   
-  if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
-    return nil;
-
-  /* select folder */
-
-  if (![self selectFolder:_url inClient:client])
-    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 = [client fetchUids:_uids parts:_parts];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self errorWithFormat:@"could not fetch %d uids for url: %@",
-           [_uids count],_url];
+  /* check connection cache */
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
     return nil;
-  }
   
-  //[self logWithFormat:@"RESULT: %@", result];
-  return (id)result;
+  return [entry fetchUIDs:_uids inURL:_url parts:_parts];
 }
 
 - (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd {
-  NGImap4Client *client;
-  NSString *p;
-  id result;
-  
-  if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
-    return nil; // TODO: return error?
-  
-  /* select folder */
-  
-  p = [self imap4FolderNameForURL:_url removeFileName:NO];
-  if (![self selectFolder:p inClient:client])
-    return nil;
+  SOGoMailConnectionEntry *entry;
   
-  /* expunge */
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
   
-  result = [client expunge];
-
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self errorWithFormat:@"could not expunge url: %@", _url];
-    return nil;
-  }
-  //[self logWithFormat:@"RESULT: %@", result];
-  return nil;
+  return [entry expungeAtURL:_url];
 }
 
 - (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd{
-  // currently returns a dict
-  NGImap4Client *client;
-  NSDictionary  *result;
-  NSString *uid;
+  SOGoMailConnectionEntry *entry;
   
   if (![_url isNotNull]) return nil;
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
   
-  if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
-    return nil;
-  
-  /* select folder */
-  
-  if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES]
-            inClient:client])
-    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;
+  return [entry fetchURL:_url parts:_parts];
 }
 
 - (NSData *)fetchContentOfBodyPart:(NSString *)_partId
   atURL:(NSURL *)_url password:(NSString *)_pwd
 {
-  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 password:_pwd];
-  
-  /* 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;
+  SOGoMailConnectionEntry *entry;
+
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return nil; // TODO: improve?
+
+  return [entry fetchContentOfBodyPart:_partId atURL:_url];
 }
 
 - (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f
   toURL:(NSURL *)_url password:(NSString *)_p
 {
-  NGImap4Client *client;
-  id result;
-  
-  if (![_url isNotNull]) return nil;
-  if (![_f   isNotNull]) return nil;
-  
-  if (![_f isKindOfClass:[NSArray class]])
-    _f = [NSArray arrayWithObjects:&_f count:1];
-  
-  /* get client */
+  SOGoMailConnectionEntry *entry;
 
-  if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
-    // TODO: return 401?
-    return nil;
-  
-  /* select folder */
-  
-  if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES]
-            inClient:client]) {
-    return [NSException exceptionWithHTTPStatus:404 /* server error */
-                       reason:@"could not select IMAP4 folder"];
-    return nil;
-  }
-  
-  /* store flags */
-  
-  result = [client storeUid:[[[_url path] lastPathComponent] intValue]
-                  add:[NSNumber numberWithBool:_flag]
-                  flags:_f];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    unsigned int status;
-    NSString *r;
-    
-    r = [result valueForKey:@"reason"];
-    if ([r isEqualToString:@"Permission denied"]) {
-      /* different for each server?, no error codes in IMAP4 ... */
-      status = 403 /* Forbidden */;
-    }
-    else
-      status = 500 /* internal server error */;
-    
-    [self logWithFormat:@"DEBUG: fail result %@", result];
+  if ((entry = [self entryForURL:_url password:_p]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
 
-    r = [@"Failed to add flag to IMAP4 message: " stringByAppendingString:r];
-    
-    return [NSException exceptionWithHTTPStatus:status /* server error */
-                       reason:r];
-  }
-  /* result contains 'fetch' key with the current flags */
-  return nil;
+  return [entry addOrRemove:_flag flags:_f toURL:_url];
 }
 - (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
   return [self addOrRemove:YES flags:_f toURL:_u password:_p];
@@ -718,47 +351,25 @@ static NSString       *imap4Separator  = nil;
 - (NSException *)postData:(NSData *)_data flags:(id)_f
   toFolderURL:(NSURL *)_url password:(NSString *)_p
 {
-  NGImap4Client *client;
-  id result;
-  
+  SOGoMailConnectionEntry *entry;
+
   if (![_url isNotNull]) return nil;
-  if (![_f   isNotNull]) _f = [NSArray array];
   
-  if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
-    return nil;
+  if ((entry = [self entryForURL:_url password:_p]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
   
-  if (![_f isKindOfClass:[NSArray class]])
-    _f = [NSArray arrayWithObjects:&_f count:1];
-  
-  result = [client append:_data 
-                  toFolder:[self imap4FolderNameForURL:_url]
-                  withFlags:_f];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self logWithFormat:@"DEBUG: fail result %@", result];
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:@"failed to store message to IMAP4 message"];
-  }
-  /* result contains 'fetch' key with the current flags */
-  
-  // TODO: need to flush any caches?
-  return nil;
+  return [entry postData:_data flags:_f toFolderURL:_url];
 }
 
 - (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl
   password:(NSString *)_pwd
 {
   SOGoMailConnectionEntry *entry;
-  NSString *srcname, *destname;
-  unsigned uid;
-  id result;
   
   /* check connection cache */
   
-  if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
+  if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_srcurl];
   
   /* check whether URLs are on different servers */
   
@@ -768,30 +379,7 @@ static NSString       *imap4Separator  = nil;
                        reason:@"source and destination on different servers"];
   }  
   
-  /* names */
-  
-  srcname  = [self imap4FolderNameForURL:_srcurl removeFileName:YES];
-  uid      = [[[_srcurl path] lastPathComponent] unsignedIntValue];
-  destname = [self imap4FolderNameForURL:_desturl];
-
-  /* select source folder */
-  
-  if (![self selectFolder:srcname inClient:[entry client]]) {
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find source folder"];
-  }
-  
-  /* copy */
-  
-  result = [[entry client] copyUid:uid toFolder:destname];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    return [NSException exceptionWithHTTPStatus:500 /* Server Error */
-                       reason:@"copy operation failed"];
-  }
-  
-  // TODO: need to flush some caches?
-  
-  return nil;
+  return [entry copyMailURL:_srcurl toFolderURL:_desturl];
 }
 
 /* managing folders */
@@ -806,140 +394,54 @@ static NSString       *imap4Separator  = nil;
 
 - (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  NSString        *folderName;
-  id result;
   
   if ((entry = [self entryForURL:_url password:_pwd]) == nil)
     return NO;
   
-  /* check in hierarchy cache */
-  
-  if ((result = [entry 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 = [[entry client] select:folderName];
-  if (![[result valueForKey:@"result"] boolValue])
-    return NO;
-  
-  return YES;
+  return [entry doesMailboxExistAtURL:_url];
 }
 
 - (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  SOGoMailboxInfo *info;
-  NSString        *folderName;
-  id result;
-  
-  if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
   
-  folderName = [self imap4FolderNameForURL:_url];
-  result     = [[entry client] select:folderName];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (select failed)"];
-  }
-
-  info = [[SOGoMailboxInfo alloc] initWithURL:_url folderName:folderName
-                                 selectDictionary:result];
-  return [info autorelease];
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
+  
+  return [entry infoForMailboxAtURL:_url];
 }
 
 - (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url
   password:(NSString *)_pwd
 {
   SOGoMailConnectionEntry *entry;
-  NSString *newPath;
-  id       result;
-
-  /* check connection cache */
   
-  if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
+  /* check connection cache */
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
 
-  /* construct path */
-  
-  newPath = [self imap4FolderNameForURL:_url];
-  newPath = [newPath stringByAppendingString:[self imap4Separator]];
-  newPath = [newPath stringByAppendingString:_mailbox];
-  
-  /* create */
-  
-  result = [[entry client] create:newPath];
-  if ([self isPermissionDeniedResult:result]) {
-    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
-                       reason:@"creation of folders not allowed"];
-  }
-  else if ([[result valueForKey:@"result"] intValue] == 0) {
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:[result valueForKey:@"reason"]];
-  }
-  
-  [entry flushFolderHierarchyCache];
-  // [self debugWithFormat:@"created mailbox: %@: %@", newPath, result];
-  return nil;
+  return [entry createMailbox:_mailbox atURL:_url];
 }
 
 - (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  NSString *path;
-  id       result;
-
-  /* check connection cache */
   
-  if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
-  
-  /* delete */
-  
-  path   = [self imap4FolderNameForURL:_url];
-  result = [[entry client] delete:path];
+  /* check connection cache */
   
-  if ([self isPermissionDeniedResult:result]) {
-    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
-                       reason:@"creation of folders not allowed"];
-  }
-  else if ([[result valueForKey:@"result"] intValue] == 0) {
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:[result valueForKey:@"reason"]];
-  }
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
   
-  [entry flushFolderHierarchyCache];
-#if 0
-  [self debugWithFormat:@"delete mailbox %@: %@", _url, result];
-#endif
-  return nil;
+  return [entry deleteMailboxAtURL:_url];
 }
 
 - (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl
   password:(NSString *)_pwd
 {
   SOGoMailConnectionEntry *entry;
-  NSString *srcname, *destname;
-  id result;
   
   /* check connection cache */
   
-  if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
+  if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil)
+    return [self errorForMissingEntryAtURL:_srcurl];
   
   /* check whether URLs are on different servers */
   
@@ -949,27 +451,7 @@ static NSString       *imap4Separator  = nil;
                        reason:@"source and destination on different servers"];
   }  
   
-  /* rename */
-  
-  srcname  = [self imap4FolderNameForURL:_srcurl];
-  destname = [self imap4FolderNameForURL:_desturl];
-  
-  result = [[entry client] rename:srcname to:destname];
-  
-  if ([self isPermissionDeniedResult:result]) {
-    return [NSException exceptionWithHTTPStatus:403 /* forbidden */
-                       reason:@"creation of folders not allowed"];
-  }
-  else if ([[result valueForKey:@"result"] intValue] == 0) {
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:[result valueForKey:@"reason"]];
-  }
-  
-  [entry flushFolderHierarchyCache];
-#if 0
-  [self debugWithFormat:@"renamed mailbox %@: %@", _srcurl, result];
-#endif
-  return nil;
+  return [entry moveMailboxAtURL:_srcurl toURL:_desturl];
 }
 
 - (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
@@ -979,57 +461,20 @@ static NSString       *imap4Separator  = nil;
       root     = lrswipcda;
   */
   SOGoMailConnectionEntry *entry;
-  NSString *folderName;
-  id       result;
   
-  if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
-  
-  folderName = [self imap4FolderNameForURL:_url];
-  result     = [[entry client] getACL:folderName];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self logWithFormat:@"ERROR: getacl failed: %@", result];
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find ACL for IMAP4 folder"];
-  }
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return (id)[self errorForMissingEntryAtURL:_url];
   
-  return [result valueForKey:@"acl"];
+  return [entry aclForMailboxAtURL:_url];
 }
 
 - (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
   SOGoMailConnectionEntry *entry;
-  NSString *folderName;
-  id       result;
-  
-  if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
-    // TODO: better to use an auth exception?
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find IMAP4 folder (no entry)"];
-  }
-
-  /* check cache */
   
-  if ((result = [entry cachedMyRightsForURL:_url]) != nil)
-    return result;
-
-  /* run IMAP4 op */
-  
-  folderName = [self imap4FolderNameForURL:_url];
-  result     = [[entry client] myRights:folderName];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    [self logWithFormat:@"ERROR: myrights failed: %@", result];
-    return [NSException exceptionWithHTTPStatus:404 /* Not Found */
-                       reason:@"did not find myrights for IMAP4 folder"];
-  }
+  if ((entry = [self entryForURL:_url password:_pwd]) == nil)
+    return (id)[self errorForMissingEntryAtURL:_url];
 
-  /* cache results */
-  
-  if ((result = [result valueForKey:@"myrights"]) != nil)
-    [entry cacheMyRights:result forURL:_url];
-  return result;
+  return [entry myRightsForMailboxAtURL:_url];
 }
 
 /* bulk flag adding (eg used for empty/trash) */
@@ -1037,49 +482,15 @@ static NSString       *imap4Separator  = nil;
 - (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url
   password:(NSString *)_p
 {
-  NGImap4Client *client;
-  id       result;
+  SOGoMailConnectionEntry *entry;
   
   if (![_url isNotNull]) return nil;
   if (![_f   isNotNull]) return nil;
-
-  if (![_f isKindOfClass:[NSArray class]])
-    _f = [NSArray arrayWithObjects:&_f count:1];
-  
-  /* get client */
-  
-  if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
-    // TODO: return 401?
-    return nil;
-  
-  /* select folder */
-  
-  if (![self selectFolder:[self imap4FolderNameForURL:_url] inClient:client]) {
-    return [NSException exceptionWithHTTPStatus:404 /* not found */
-                       reason:@"could not select IMAP4 folder"];
-  }
-
-  /* fetch all sequence numbers */
   
-  result = [client searchWithQualifier:nil /* means: ALL */];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:@"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 = [client storeFlags:_f forMSNs:result addOrRemove:YES];
-  if (![[result valueForKey:@"result"] boolValue]) {
-    return [NSException exceptionWithHTTPStatus:500 /* server error */
-                       reason:@"could not change flags in IMAP4 folder"];
-  }
+  if ((entry = [self entryForURL:_url password:_p]) == nil)
+    return [self errorForMissingEntryAtURL:_url];
   
-  return nil;
+  return [entry addFlags:_f toAllMessagesInURL:_url];
 }
 
 /* debugging */
index c2722d2da806c1d61e56e73fcd89d6d84decda94..67dd1fc6015908e72019e23ae84936894bcf91b5 100644 (file)
@@ -1,6 +1,6 @@
 # Version file
 
-SUBMINOR_VERSION:=94
+SUBMINOR_VERSION:=95
 
 # v0.9.91 requires libNGMime       v4.5.222
 # v0.9.69 requires libNGMime       v4.5.210