Extras/GrowlMail/Message+GrowlMail.m
author Peter Hosey
Tue Feb 24 20:17:30 2009 -0800 (2009-02-24)
changeset 4170 caae0ed9a580
parent 4001 823a48a1b49d
child 4171 2918a61fe9b3
permissions -rw-r--r--
Renamed -[Message showNotification] to -[Message GMShowNotification] to prevent namespace collisions if Apple ever adds a showNotification method of its own to the Message class.
tick@796
     1
/*
ingmarstein@1156
     2
 Copyright (c) The Growl Project, 2004-2005
tick@796
     3
 All rights reserved.
ingmarstein@1156
     4
ingmarstein@1156
     5
 Redistribution and use in source and binary forms, with or without modification,
ingmarstein@1156
     6
 are permitted provided that the following conditions are met:
ingmarstein@1156
     7
tick@796
     8
 1. Redistributions of source code must retain the above copyright
tick@796
     9
 notice, this list of conditions and the following disclaimer.
tick@796
    10
 2. Redistributions in binary form must reproduce the above copyright
tick@796
    11
 notice, this list of conditions and the following disclaimer in the
tick@796
    12
 documentation and/or other materials provided with the distribution.
tick@796
    13
 3. Neither the name of Growl nor the names of its contributors
tick@796
    14
 may be used to endorse or promote products derived from this software
tick@796
    15
 without specific prior written permission.
tick@796
    16
ingmarstein@1156
    17
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ingmarstein@1156
    18
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
ingmarstein@1156
    19
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
ingmarstein@1156
    20
 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
ingmarstein@1156
    21
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
ingmarstein@1156
    22
 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
ingmarstein@1156
    23
 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
ingmarstein@1156
    24
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
ingmarstein@1156
    25
 OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
ingmarstein@1156
    26
 OF THE POSSIBILITY OF SUCH DAMAGE.
ingmarstein@1156
    27
*/
ingmarstein@563
    28
//
ingmarstein@563
    29
//  Message+GrowlMail.m
ingmarstein@563
    30
//  GrowlMail
ingmarstein@563
    31
//
ingmarstein@563
    32
//  Created by Ingmar Stein on 27.10.04.
ingmarstein@563
    33
//
ingmarstein@563
    34
ingmarstein@563
    35
#import "Message+GrowlMail.h"
ingmarstein@596
    36
#import "GrowlMail.h"
ingmarstein@1983
    37
#import <AddressBook/AddressBook.h>
boredzo@1278
    38
#import <Growl/Growl.h>
ingmarstein@563
    39
evands@4001
    40
@interface NSString (GrowlMail_KeywordReplacing)
evands@4001
    41
evands@4001
    42
- (NSString *) stringByReplacingKeywords:(NSArray *)keywords
evands@4001
    43
                              withValues:(NSArray *)values;
evands@4001
    44
evands@4001
    45
@end
evands@4001
    46
evands@4001
    47
@interface NSMutableString (GrowlMail_LineOrientedTruncation)
evands@4001
    48
evands@4001
    49
- (void) trimStringToFirstNLines:(unsigned)n;
evands@4001
    50
evands@4001
    51
@end
evands@4001
    52
evands@4001
    53
@implementation Message (GrowlMail)
evands@4001
    54
/*!
evands@4001
    55
 * @brief Show a Growl notification for this message
evands@4001
    56
 *
evands@4001
    57
 * This should be called on an auxiliary thread as it may block.
evands@4001
    58
 */
Peter@4170
    59
- (void) GMShowNotification
evands@4001
    60
{
evands@4001
    61
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
evands@4001
    62
evands@4001
    63
	NSString *account = (NSString *)[[[self mailbox] account] displayName];
ingmarstein@581
    64
	NSString *sender = [self sender];
ingmarstein@581
    65
	NSString *senderAddress = [sender uncommentedAddress];
evands@4001
    66
	NSString *subject = (NSString *)[self subject];
evands@4001
    67
	NSString *body;
evands@4001
    68
	NSString *titleFormat = (NSString *)GMTitleFormatString();
evands@4001
    69
	NSString *descriptionFormat = (NSString *)GMDescriptionFormatString();
evands@4001
    70
evands@4001
    71
	if ([titleFormat rangeOfString:@"%body"].location != NSNotFound ||
evands@4001
    72
			[descriptionFormat rangeOfString:@"%body"].location != NSNotFound) {
ingmarstein@3191
    73
		/* We will need the body */
ingmarstein@3192
    74
		MessageBody *messageBody = [self messageBodyIfAvailable];
evands@4001
    75
		int nonBlockingAttempts = 0;
evands@4001
    76
		while (!messageBody && nonBlockingAttempts < 3) {
ingmarstein@3192
    77
			/* No message body available yet, but we need one */
evands@4001
    78
			nonBlockingAttempts++;
evands@4001
    79
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:(0.5 * nonBlockingAttempts)]];
evands@4001
    80
			
evands@4001
    81
			/* We'd prefer to let whatever Mail process might want the message body get it on its own terms rather than blocking on this thread */
evands@4001
    82
			messageBody = [self messageBodyIfAvailable];
ingmarstein@3192
    83
		}
ingmarstein@3192
    84
evands@4001
    85
		/* Already tried three times (3 seconds); this time, block this thread to get it. */ 
evands@4001
    86
		if (!messageBody) messageBody = [self messageBody];
evands@4001
    87
ingmarstein@3191
    88
		if (messageBody) {
evands@3812
    89
			NSString *originalBody = nil;
evands@3812
    90
			/* stringForIndexing selector: Mail.app 3.0 in OS X 10.4, not in 10.5. */
ingmarstein@3191
    91
			if ([messageBody respondsToSelector:@selector(stringForIndexing)])
ingmarstein@3191
    92
				originalBody = [messageBody stringForIndexing];
evands@3812
    93
			else if ([messageBody respondsToSelector:@selector(attributedString)])
evands@3812
    94
				originalBody = [[messageBody attributedString] string];
evands@3812
    95
			else if ([messageBody respondsToSelector:@selector(stringValueForJunkEvaluation:)])
ingmarstein@3191
    96
				originalBody = [messageBody stringValueForJunkEvaluation:NO];
evands@3812
    97
			if (originalBody) {
evands@4001
    98
				NSMutableString *transformedBody = [[[originalBody stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy] autorelease];
evands@4001
    99
				unsigned lengthWithoutWhitespace = [transformedBody length];
evands@4001
   100
				[transformedBody trimStringToFirstNLines:4U];
evands@4001
   101
				unsigned length = [transformedBody length];
evands@4001
   102
				if (length > 200U) {
evands@4001
   103
					[transformedBody deleteCharactersInRange:NSMakeRange(200U, length - 200U)];
evands@4001
   104
					length = 200U;
evands@3812
   105
				}
evands@3812
   106
				if (length != lengthWithoutWhitespace)
evands@4001
   107
					[transformedBody appendString:[NSString stringWithUTF8String:"\xE2\x80\xA6"]];
evands@4001
   108
				body = (NSString *)transformedBody;
evands@3812
   109
			} else {
evands@4001
   110
				body = @"";	
ingmarstein@3191
   111
			}
ingmarstein@3191
   112
		} else {
evands@4001
   113
			body = @"";
ingmarstein@2249
   114
		}
ingmarstein@2249
   115
	} else
evands@4001
   116
		body = @"";
ingmarstein@622
   117
ingmarstein@622
   118
	/* The fullName selector is not available in Mail.app 2.0. */
ingmarstein@2249
   119
	if ([sender respondsToSelector:@selector(fullName)])
ingmarstein@622
   120
		sender = [sender fullName];
ingmarstein@2249
   121
	else if ([sender addressComment])
ingmarstein@622
   122
		sender = [sender addressComment];
ingmarstein@2249
   123
evands@4001
   124
	NSArray *keywords = [NSArray arrayWithObjects:
evands@4001
   125
		@"%sender",
evands@4001
   126
		@"%subject",
evands@4001
   127
		@"%body",
evands@4001
   128
		@"%account",
evands@4001
   129
		nil];
evands@4001
   130
	NSArray *values = [NSArray arrayWithObjects:
evands@4001
   131
		(sender ? sender : @""),
evands@4001
   132
		(subject ? subject : @""),
evands@4001
   133
		(body ? body : @""),
evands@4001
   134
		(account ? account : @""),
evands@4001
   135
		 nil];
evands@4001
   136
	NSString *title = [titleFormat stringByReplacingKeywords:keywords withValues:values];
evands@4001
   137
	NSString *description = [descriptionFormat stringByReplacingKeywords:keywords withValues:values];
ingmarstein@2311
   138
ingmarstein@2311
   139
	/*
ingmarstein@1983
   140
	NSLog(@"Subject: '%@'", subject);
ingmarstein@1983
   141
	NSLog(@"Sender: '%@'", sender);
ingmarstein@1983
   142
	NSLog(@"Account: '%@'", account);
ingmarstein@1983
   143
	NSLog(@"Body: '%@'", body);
ingmarstein@3190
   144
	*/
ingmarstein@3190
   145
ingmarstein@1983
   146
	/*
ingmarstein@1983
   147
	 * MailAddressManager fetches images asynchronously so they might arrive
ingmarstein@1983
   148
	 * after we have sent our notification.
ingmarstein@1983
   149
	 */
ingmarstein@1983
   150
	/*
ingmarstein@581
   151
	MailAddressManager *addressManager = [MailAddressManager addressManager];
ingmarstein@1983
   152
	[addressManager fetchImageForAddress:senderAddress];
ingmarstein@1983
   153
	NSImage *image = [addressManager imageForMailAddress:senderAddress];
ingmarstein@1983
   154
	*/
ingmarstein@1983
   155
	ABSearchElement *personSearch = [ABPerson searchElementForProperty:kABEmailProperty
ingmarstein@1983
   156
																 label:nil
ingmarstein@1983
   157
																   key:nil
ingmarstein@1983
   158
																 value:senderAddress
ingmarstein@1983
   159
															comparison:kABEqualCaseInsensitive];
ingmarstein@2249
   160
ingmarstein@2306
   161
	NSData *image = nil;
ingmarstein@2282
   162
	NSEnumerator *matchesEnum = [[[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:personSearch] objectEnumerator];
boredzo@2279
   163
	ABPerson *person;
boredzo@2279
   164
	while ((!image) && (person = [matchesEnum nextObject]))
boredzo@2281
   165
		image = [person imageData];
boredzo@2278
   166
ingmarstein@2282
   167
	//no matches in the Address Book with an icon, so use Mail's icon instead.
ingmarstein@2282
   168
	if (!image)
ingmarstein@2306
   169
		image = [[NSImage imageNamed:@"NSApplicationIcon"] TIFFRepresentation];
ingmarstein@2249
   170
evands@4001
   171
	NSString *notificationName;
evands@4001
   172
	if ([self isJunk] || ([[MailAccount junkMailboxUids] containsObject:[self mailbox]])) {
evands@4001
   173
		notificationName = NEW_JUNK_MAIL_NOTIFICATION;
evands@3763
   174
	} else {
evands@3763
   175
		if ([self respondsToSelector:@selector(type)] && [self type] == MESSAGE_TYPE_NOTE) {
evands@4001
   176
			notificationName = NEW_NOTE_NOTIFICATION;
evands@3763
   177
		} else {
evands@4001
   178
			notificationName = NEW_MAIL_NOTIFICATION;
evands@3763
   179
		}
evands@3763
   180
	}
ingmarstein@2311
   181
ingmarstein@2562
   182
	NSString *clickContext = [self messageID];
ingmarstein@2562
   183
evands@4001
   184
	[GrowlApplicationBridge notifyWithTitle:title
evands@4001
   185
								description:description
evands@4001
   186
						   notificationName:notificationName
ingmarstein@2221
   187
								   iconData:image
ingmarstein@2103
   188
								   priority:0
ingmarstein@2103
   189
								   isSticky:NO
ingmarstein@2562
   190
							   clickContext:clickContext];	// non-nil click context
evands@4001
   191
evands@4001
   192
	[GrowlMail didFinishNotificationForMessage:self];
evands@4001
   193
evands@4001
   194
	[pool release];
ingmarstein@563
   195
}
ingmarstein@3192
   196
evands@4001
   197
@end
evands@4001
   198
evands@4001
   199
@implementation NSString (GrowlMail_KeywordReplacing)
evands@4001
   200
evands@4001
   201
- (NSString *) stringByReplacingKeywords:(NSArray *)keywords
evands@4001
   202
                              withValues:(NSArray *)values
evands@4001
   203
{
evands@4001
   204
	NSParameterAssert([keywords count] == [values count]);
evands@4001
   205
	NSMutableString *str = [[self mutableCopy] autorelease];
evands@4001
   206
evands@4001
   207
	NSEnumerator *keywordsEnum = [keywords objectEnumerator], *valuesEnum = [values objectEnumerator];
evands@4001
   208
	NSString *keyword, *value;
evands@4001
   209
	while ((keyword = [keywordsEnum nextObject]) && (value = [valuesEnum nextObject])) {
evands@4001
   210
		[str replaceOccurrencesOfString:keyword
evands@4001
   211
		                     withString:value
evands@4001
   212
		                        options:0
evands@4001
   213
		                          range:NSMakeRange(0, [str length])];
evands@4001
   214
	}
evands@4001
   215
	return str;
ingmarstein@3192
   216
}
ingmarstein@3192
   217
ingmarstein@563
   218
@end
evands@4001
   219
evands@4001
   220
@implementation NSMutableString (GrowlMail_LineOrientedTruncation)
evands@4001
   221
evands@4001
   222
- (void) trimStringToFirstNLines:(unsigned)n {
evands@4001
   223
	NSRange range;
evands@4001
   224
	unsigned end;
evands@4001
   225
	unsigned length;
evands@4001
   226
evands@4001
   227
	range.location = 0;
evands@4001
   228
	range.length = 0;
evands@4001
   229
	for (unsigned i=0U; i<n; ++i)
evands@4001
   230
		[self getLineStart:NULL end:&range.location contentsEnd:&end forRange:range];
evands@4001
   231
evands@4001
   232
	length = [self length];
evands@4001
   233
	if (length > end)
evands@4001
   234
		[self deleteCharactersInRange:NSMakeRange(end, length - end)];
evands@4001
   235
}
evands@4001
   236
evands@4001
   237
@end