]> err.no Git - scalable-opengroupware.org/commitdiff
added ability to create NGMime objects
authorhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 27 Oct 2004 01:23:05 +0000 (01:23 +0000)
committerhelge <helge@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 27 Oct 2004 01:23:05 +0000 (01:23 +0000)
git-svn-id: http://svn.opengroupware.org/SOGo/trunk@428 d1b88da0-ebda-0310-925b-ed51d893ca5b

SOGo/SoObjects/Mailer/ChangeLog
SOGo/SoObjects/Mailer/SOGoDraftObject.h
SOGo/SoObjects/Mailer/SOGoDraftObject.m
SOGo/SoObjects/Mailer/Version
SOGo/UI/Mailer/ChangeLog
SOGo/UI/Mailer/UIxMailEditor.m
SOGo/UI/Mailer/Version

index 04f9120689caeac4341e6a565082bb15b3c1da95..f89955bfd892bb0f4002c6a03f5bae902c88433f 100644 (file)
@@ -1,3 +1,8 @@
+2004-10-27  Helge Hess  <helge.hess@opengroupware.org>
+
+       * SOGoDraftObject.m: added ability to create NGMime objects from draft
+         (v0.9.40)
+
 2004-10-26  Helge Hess  <helge.hess@opengroupware.org>
 
        * SOGoDraftObject.[hm]: added method to delete attachments (v0.9.39)
index 9a0aedc405e5b603197ef97b5a6996b8f7ba2749..65d6f200179b443b238af28fa62822e5b910b565 100644 (file)
 */
 
 @class NSString, NSArray, NSDictionary, NSData;
+@class NGMimeMessage;
 
 @interface SOGoDraftObject : SOGoMailBaseObject
 {
-  NSString *path;
+  NSString     *path;
+  NSDictionary *info; /* stores the envelope information */
 }
 
 /* contents */
 - (BOOL)saveAttachment:(NSData *)_attachment withName:(NSString *)_name;
 - (BOOL)deleteAttachmentWithName:(NSString *)_name;
 
+/* NGMime representations */
+
+- (NGMimeMessage *)mimeMessage;
+
 @end
 
 #endif /* __Mailer_SOGoDraftObject_H__ */
index 722ceaa5699862356b01b1971616143a0888e6a9..16c893ea6f137fa7dfc1ab5981b411384394dc35 100644 (file)
 */
 
 #include "SOGoDraftObject.h"
+#include <NGMail/NGMimeMessage.h>
+#include <NGMime/NGMimeBodyPart.h>
+#include <NGMime/NGMimeMultipartBody.h>
+#include <NGMime/NGMimeFileData.h>
+#include <NGMime/NGMimeType.h>
 #include <NGExtensions/NSFileManager+Extensions.h>
 #include "common.h"
 
 @implementation SOGoDraftObject
 
+static NGMimeType *TextPlainType  = nil;
+static NGMimeType *MultiMixedType = nil;
+
++ (void)initialize {
+  TextPlainType  = [[NGMimeType mimeType:@"text"      subType:@"plain"]  copy];
+  MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"]  copy];
+}
+
 - (void)dealloc {
+  [self->info release];
   [self->path release];
   [super dealloc];
 }
   return YES;
 }
 - (NSDictionary *)fetchInfo {
-  NSDictionary *info;
   NSString *p;
+
+  if (self->info != nil)
+    return self->info;
   
   p = [self infoPath];
   if (![[self spoolFileManager] fileExistsAtPath:p]) {
     return nil;
   }
   
-  if ((info = [NSDictionary dictionaryWithContentsOfFile:p]) == nil)
-    [self logWithFormat:@"ERROR: dictionary broken at path: %@", p];
+  self->info = [[NSDictionary alloc] initWithContentsOfFile:p];
+  if (self->info == nil)
+    [self logWithFormat:@"ERROR: draft info dictionary broken at path: %@", p];
   
-  return info;
+  return self->info;
 }
 
 - (NSArray *)fetchAttachmentNames {
   if (r.length > 0) return NO;
   r = [_name rangeOfString:@"~"];
   if (r.length > 0) return NO;
+  r = [_name rangeOfString:@"\""];
+  if (r.length > 0) return NO;
   
   return YES;
 }
 
+- (NSString *)pathToAttachmentWithName:(NSString *)_name {
+  if ([_name length] == 0)
+    return nil;
+  
+  return [[self draftFolderPath] stringByAppendingPathComponent:_name];
+}
+
 - (BOOL)saveAttachment:(NSData *)_attachment withName:(NSString *)_name {
   NSString *p;
 
   if (![self isValidAttachmentName:_name])
     return NO;
   
-  p = [[self draftFolderPath] stringByAppendingPathComponent:_name];
+  p = [self pathToAttachmentWithName:_name];
   return [_attachment writeToFile:p atomically:YES];
 }
 
     return NO;
   
   fm = [self spoolFileManager];
-  p  = [[self draftFolderPath] stringByAppendingPathComponent:_name];
+  p  = [self pathToAttachmentWithName:_name];
   if (![fm fileExistsAtPath:p])
     return YES; /* well, doesn't exist, so its deleted ;-) */
   
   return [fm removeFileAtPath:p handler:nil];
 }
 
+/* NGMime representations */
+
+- (NGMimeBodyPart *)bodyPartForText {
+  NGMutableHashMap *map;
+  NGMimeBodyPart   *bodyPart;
+  NSDictionary     *lInfo;
+
+  if ((lInfo = [self fetchInfo]) == nil)
+    return nil;
+  
+  /* prepare header of body part */
+  
+  map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
+  [map setObject:@"text/plain" forKey:@"content-type"];
+  
+  /* prepare body content */
+  
+  bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
+  [bodyPart setBody:[lInfo objectForKey:@"text"]];
+  return bodyPart;
+}
+
+- (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map {
+  NSDictionary  *lInfo;
+  NGMimeMessage *message;  
+
+  if ((lInfo = [self fetchInfo]) == nil)
+    return nil;
+  
+  [map setObject:@"text/plain" forKey:@"content-type"];
+  message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
+  [message setBody:[lInfo objectForKey:@"text"]];
+  return message;
+}
+
+- (NSString *)mimeTypeForExtension:(NSString *)_ext {
+  // TODO: make configurable
+  if ([_ext isEqualToString:@"txt"])  return @"text/plain";
+  if ([_ext isEqualToString:@"html"]) return @"text/html";
+  if ([_ext isEqualToString:@"htm"])  return @"text/html";
+  if ([_ext isEqualToString:@"gif"])  return @"image/gif";
+  if ([_ext isEqualToString:@"jpg"])  return @"image/jpeg";
+  if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
+  return @"application/octet-stream";
+}
+
+- (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
+  return [self mimeTypeForExtension:[_name pathExtension]];
+}
+- (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
+  NSString *type;
+  NSString *cdtype;
+  NSString *cd;
+  
+  type = [self contentTypeForAttachmentWithName:_name];
+  
+  if ([type hasPrefix:@"text/"])
+    cdtype = @"inline";
+  else if ([type hasPrefix:@"image/"])
+    cdtype = @"inline";
+  else
+    cdtype = @"attachment";
+
+  cd = [cdtype stringByAppendingString:@"; filename=\""];
+  cd = [cd stringByAppendingString:_name];
+  cd = [cd stringByAppendingString:@"\""];
+  
+  // TODO: add size parameter (useful addition, RFC 2183)
+  return cd;
+}
+
+- (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
+  NSFileManager    *fm;
+  NGMutableHashMap *map;
+  NGMimeBodyPart   *bodyPart;
+  NSString         *s;
+  NSData           *content;
+  BOOL             attachAsString;
+  NSString         *p;
+
+  if (_name == nil) return nil;
+
+  /* check attachment */
+  
+  fm = [self spoolFileManager];
+  p  = [self pathToAttachmentWithName:_name];
+  if (![fm isReadableFileAtPath:p]) {
+    [self logWithFormat:@"ERROR: did not find attachment: '%@'", _name];
+    return nil;
+  }
+  attachAsString = NO;
+  
+  /* prepare header of body part */
+
+  map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
+
+  if ((s = [self contentTypeForAttachmentWithName:_name])) {
+    [map setObject:s forKey:@"content-type"];
+    if ([s hasPrefix:@"text/"])
+      attachAsString = YES;
+  }
+  if ((s = [self contentDispositionForAttachmentWithName:_name]))
+    [map setObject:s forKey:@"content-disposition"];
+  
+  bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
+  
+  /* prepare body content */
+  
+  if (attachAsString) { // TODO: is this really necessary?
+    NSString *s;
+    
+    content = [[NSData alloc] initWithContentsOfMappedFile:p];
+    
+    s = [[NSString alloc] initWithData:content
+                         encoding:[NSString defaultCStringEncoding]];
+    if (s != nil) {
+      [bodyPart setBody:s];
+      [s release]; s = nil;
+    }
+    else {
+      [self logWithFormat:
+             @"WARNING: could not get text attachment as string: '%@'",_name];
+      [bodyPart setBody:content];
+    }
+  }
+  else {
+    content = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
+    [bodyPart setBody:content];
+  }
+  
+  [content release]; content = nil;
+  
+  return bodyPart;
+}
+
+- (NSArray *)bodyPartsForAllAttachments {
+  /* returns nil on error */
+  NSMutableArray *bodyParts;
+  NSArray  *names;
+  unsigned i, count;
+  
+  names = [self fetchAttachmentNames];
+  if ((count = [names count]) == 0)
+    return [NSArray array];
+  
+  bodyParts = [NSMutableArray arrayWithCapacity:count];
+  for (i = 0; i < count; i++) {
+    NGMimeBodyPart *bodyPart;
+    
+    bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
+    if (bodyPart == nil)
+      return nil;
+    
+    [bodyParts addObject:bodyPart];
+  }
+  return bodyParts;
+}
+
+- (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
+  andBodyParts:(NSArray *)_bodyParts
+{
+  NGMimeMessage       *message;  
+  NGMimeMultipartBody *mBody;
+  NGMimeBodyPart      *part;
+  NSEnumerator        *e;
+  
+  [map addObject:MultiMixedType forKey:@"content-type"];
+    
+  message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
+  mBody   = [[NGMimeMultipartBody alloc] initWithPart:message];
+  
+  part = [self bodyPartForText];
+  [mBody addBodyPart:part];
+  
+  e = [_bodyParts objectEnumerator];
+  while ((part = [e nextObject]) != nil)
+    [mBody addBodyPart:part];
+  
+  [message setBody:mBody];
+  [mBody release]; mBody = nil;
+  return message;
+}
+
+- (NGMutableHashMap *)mimeHeaderMap {
+  NGMutableHashMap *map;
+  NSDictionary *lInfo;
+  NSArray      *emails;
+  NSString     *s;
+  
+  if ((lInfo = [self fetchInfo]) == nil)
+    return nil;
+  
+  map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
+  
+  /* add recipients */
+  
+  if ((emails = [lInfo objectForKey:@"to"]) != nil) {
+    if ([emails count] == 0) {
+      [self logWithFormat:@"ERROR: missing 'to' recipient in email!"];
+      return nil;
+    }
+    [map setObjects:emails forKey:@"to"];
+  }
+  if ((emails = [lInfo objectForKey:@"cc"]) != nil)
+    [map setObjects:emails forKey:@"cc"];
+  if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
+    [map setObjects:emails forKey:@"bcc"];
+  
+  /* add senders */
+  
+  if ((s = [lInfo objectForKey:@"from"]) != nil)
+    [map setObject:s forKey:@"from"];
+  if ((s = [lInfo objectForKey:@"replyTo"]) != nil)
+    [map setObject:s forKey:@"reply-to"];
+  
+  /* add subject */
+  
+  if ((s = [lInfo objectForKey:@"subject"]) != nil)
+    [map setObject:s forKey:@"subject"];
+  
+  /* add standard headers */
+  
+  [map addObject:[NSCalendarDate date] forKey:@"date"];
+  [map addObject:@"1.0"                forKey:@"MIME-Version"];
+  [map addObject:@"SOGoMail 1.0"       forKey:@"X-Mailer"];
+  
+  return map;
+}
+
+- (NGMimeMessage *)mimeMessage {
+  NSAutoreleasePool *pool;
+  NGMutableHashMap  *map;
+  NSArray           *bodyParts;
+  NGMimeMessage     *message;
+  
+  pool = [[NSAutoreleasePool alloc] init];
+  
+  if ([self fetchInfo] == nil) {
+    [self logWithFormat:@"ERROR: could not locate draft fetch info!"];
+    return nil;
+  }
+  
+  if ((map = [self mimeHeaderMap]) == nil)
+    return nil;
+  [self logWithFormat:@"MIME Envelope: %@", map];
+  
+  if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
+    [self logWithFormat:
+           @"ERROR: could not create body parts for attachments!"];
+    return nil; // TODO: improve error handling, return exception
+  }
+  [self logWithFormat:@"attachments: %@", bodyParts];
+  
+  if ([bodyParts count] == 0) {
+    /* no attachments */
+    message = [self mimeMessageForContentWithHeaderMap:map];
+  }
+  else {
+    /* attachments, create multipart/mixed */
+    message = [self mimeMultiPartMessageWithHeaderMap:map 
+                   andBodyParts:bodyParts];
+  }
+  [self logWithFormat:@"message: %@", message];
+
+  message = [message retain];
+  [pool release];
+  return [message autorelease];
+}
+
 @end /* SOGoDraftObject */
index 5c7b2848bb7d1745fb5cf4475cfcd8481fd15289..7c8be669329e44202d61ec9dd1818d88a8dedd45 100644 (file)
@@ -1,6 +1,6 @@
 # Version file
 
-SUBMINOR_VERSION:=39
+SUBMINOR_VERSION:=40
 
 # v0.9.35 requires SOGoLogic v0.9.24
 # v0.9.34 requires SOGoLogic v0.9.22
index acf01f5e9ee65587a9ef9a00fd3046b0065ca46e..aea7aa96306a70b1f47c603dec18094eead8931c 100644 (file)
@@ -1,3 +1,7 @@
+2004-10-27  Helge Hess  <helge.hess@opengroupware.org>
+
+       * UIxMailEditor.m: added send related code (v0.9.49)
+
 2004-10-26  Helge Hess  <helge.hess@opengroupware.org>
 
        * UIxMailEditorAttach.m: added attachment delete (v0.9.48)
index a280c94c87cc0f2f3944215188783bdde9b0b713..ac23275c6aa907b81583ae7249bf4ae54bf01919 100644 (file)
   NSArray  *bcc;
   NSString *subject;
   NSString *text;
+  NSString *tmpMessagePath;
 }
 
 @end
 
 #include <SOGo/SoObjects/Mailer/SOGoDraftObject.h>
+#include <NGMail/NGMimeMessage.h>
+#include <NGMail/NGMimeMessageGenerator.h>
 #include "common.h"
 
 @implementation UIxMailEditor
@@ -55,7 +58,23 @@ static NSArray *infoKeys = nil;
                              nil];
 }
 
+- (void)deleteTemporaryMessageFile {
+  NSFileManager *fm;
+  
+  if (![self->tmpMessagePath isNotNull])
+    return;
+
+  fm = [NSFileManager defaultManager];
+  if (![fm fileExistsAtPath:self->tmpMessagePath])
+    return;
+  
+  [fm removeFileAtPath:self->tmpMessagePath handler:nil];
+}
+
 - (void)dealloc {
+  [self deleteTemporaryMessageFile];
+  [self->tmpMessagePath release];
+  
   [self->text    release];
   [self->subject release];
   [self->to      release];
@@ -126,6 +145,22 @@ static NSArray *infoKeys = nil;
   return [self valuesForKeys:infoKeys];
 }
 
+/* MIME message */
+
+- (NSString *)saveMessageToTemporaryFile:(NGMimeMessage *)_message {
+  NGMimeMessageGenerator *gen;
+  NSString *path;
+  
+  if (![_message isNotNull])
+    return nil;
+  
+  gen  = [[NGMimeMessageGenerator alloc] init];
+  path = [gen generateMimeFromPartToFile:_message];
+  [gen release]; gen = nil;
+  
+  return path;
+}
+
 /* requests */
 
 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
@@ -134,34 +169,80 @@ static NSArray *infoKeys = nil;
 
 /* actions */
 
-- (id)editAction {
-  [self logWithFormat:@"edit action, load content from: %@",
-         [self clientObject]];
-  
-  [self loadInfo:[[self clientObject] fetchInfo]];
-  return self;
-}
-
-- (id)saveAction {
+- (BOOL)_saveFormInfo {
   NSDictionary *info;
   
   if ((info = [self storeInfo]) != nil) {
     if (![[self clientObject] storeInfo:info]) {
       [self logWithFormat:@"ERROR: failed to store draft!"];
       // TODO: improve error handling
-      return nil;
+      return NO;
     }
   }
   
-  [self logWithFormat:@"save action, store content to: %@",
-       [self clientObject]];
+  // TODO: wrap content
+  
+  return YES;
+}
+- (id)failedToSaveFormResponse {
+  // TODO: improve error handling
+  return [NSException exceptionWithHTTPStatus:500 /* server error */
+                     reason:@"failed to store draft object on server!"];
+}
+
+- (id)editAction {
+  [self logWithFormat:@"edit action, load content from: %@",
+         [self clientObject]];
+  
+  [self loadInfo:[[self clientObject] fetchInfo]];
   return self;
 }
 
+- (id)saveAction {
+  return [self _saveFormInfo] ? self : [self failedToSaveFormResponse];
+}
+
 - (id)sendAction {
-  [self logWithFormat:@"send action, store content, send mail, store: %@",
-       [self clientObject]];
+  NGMimeMessage *message;
+  
+  /* first, save form data */
+  
+  if (![self _saveFormInfo])
+    return [self failedToSaveFormResponse];
+  
+  /* then, send mail */
+  
+  if ((message = [[self clientObject] mimeMessage]) == nil) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:@"could not create MIME message for draft!"];
+  }
+  
+  self->tmpMessagePath = [[self saveMessageToTemporaryFile:message] copy];
+  if (![self->tmpMessagePath isNotNull]) {
+    return [NSException exceptionWithHTTPStatus:500 /* server error */
+                       reason:@"could not save MIME message for draft!"];
+  }
+
+  [self logWithFormat:@"saved message to: %@", self->tmpMessagePath];
+  
+  [self logWithFormat:@"TODO: send mail ..."];
+  
+  /*
+    Flags we should set:
+      if the draft is a reply   => [message markAnswered]
+      if the draft is a forward => [message addFlag:@"forwarded"]
+      
+    This is hard, we would need to find the original message in Cyrus.
+  */
+  
+  /* delete draft */
+  
+  /* finally store in Sent */
+  
+  [self logWithFormat:@"TODO: store mail in Sent folder ..."];
+  
   // if everything is ok, close the window (send a JS closing the Window)
+  [self deleteTemporaryMessageFile];
   return self;
 }
 
index 98a3496aa61b0722156afe4204584859e00a798d..13cb5e08b9f114416b2d222354b13f39bea0dd94 100644 (file)
@@ -1,6 +1,6 @@
 # $Id$
 
-SUBMINOR_VERSION:=48
+SUBMINOR_VERSION:=49
 
 # v0.9.43 requires NGObjWeb v4.3.73
 # v0.9.42 requires NGObjWeb v4.3.72