Refactored GrowlMail to have a separate singleton notifier object, in order to make the mail-bundle class less dependent on being a singleton. (Assumptions are bad, especially when you're working with private/undocumented APIs.)
authorPeter Hosey <hg@boredzo.org>
Sat Jun 06 21:30:17 2009 -0700 (2009-06-06)
changeset 4209f8a902d33769
parent 4208 10d1c49d4df1
child 4210 c36ddc8e6b22
Refactored GrowlMail to have a separate singleton notifier object, in order to make the mail-bundle class less dependent on being a singleton. (Assumptions are bad, especially when you're working with private/undocumented APIs.)
Extras/GrowlMail/GrowlMail.h
Extras/GrowlMail/GrowlMail.m
Extras/GrowlMail/GrowlMail.xcodeproj/project.pbxproj
Extras/GrowlMail/GrowlMailNotifier.h
Extras/GrowlMail/GrowlMailNotifier.m
Extras/GrowlMail/GrowlMailPreferencesModule.m
Extras/GrowlMail/GrowlMail_Prefix.pch
Extras/GrowlMail/Message+GrowlMail.m
     1.1 --- a/Extras/GrowlMail/GrowlMail.h	Fri Jun 05 21:49:20 2009 -0700
     1.2 +++ b/Extras/GrowlMail/GrowlMail.h	Sat Jun 06 21:30:17 2009 -0700
     1.3 @@ -27,42 +27,15 @@
     1.4  */
     1.5  
     1.6  #import <Cocoa/Cocoa.h>
     1.7 -#import <Growl/Growl.h>
     1.8  #import "MailHeaders.h"
     1.9 -#include <pthread.h>
    1.10  
    1.11 -#define NEW_MAIL_NOTIFICATION		@"New mail"
    1.12 -#define NEW_JUNK_MAIL_NOTIFICATION	@"New junk mail"
    1.13 -#define NEW_NOTE_NOTIFICATION		@"New note"
    1.14 +@class GrowlMailNotifier;
    1.15  
    1.16 -@interface GrowlMail : MVMailBundle <GrowlApplicationBridgeDelegate>
    1.17 +@interface GrowlMail : MVMailBundle
    1.18  {
    1.19 +	GrowlMailNotifier *notifier;
    1.20  }
    1.21  
    1.22 -/*!	@brief	Return whether the given account is enabled for notifications
    1.23 - *
    1.24 - *	@return	\c YES if GrowlMail will post notifications for this account; \c NO if it won't.
    1.25 - */
    1.26 -- (BOOL) isAccountEnabled:(MailAccount *)account;
    1.27 -/*!	@brief	Change whether the given account is enabled for notifications
    1.28 - *
    1.29 - *	@param	account	The account to enable or disable.
    1.30 - *	@param	yesOrNo	If \c YES, post notifications for messages for \a account in the future; if \c NO, don't post notifications for messages for that account.
    1.31 - */
    1.32 -- (void) setAccount:(MailAccount *)account enabled:(BOOL)yesOrNo;
    1.33 -
    1.34 -- (NSString *) applicationNameForGrowl;
    1.35 -- (NSImage *) applicationIconForGrowl;
    1.36 -- (void) growlNotificationWasClicked:(NSString *)clickContext;
    1.37 -- (NSDictionary *) registrationDictionaryForGrowl;
    1.38 -
    1.39 -+ (void)didFinishNotificationForMessage:(Message *)message;
    1.40 -
    1.41  @end
    1.42  
    1.43 -BOOL GMIsEnabled(void);
    1.44 -int  GMSummaryMode(void);
    1.45 -BOOL GMInboxOnly(void);
    1.46  NSBundle *GMGetGrowlMailBundle(void);
    1.47 -NSString *GMTitleFormatString(void);
    1.48 -NSString *GMDescriptionFormatString(void);
     2.1 --- a/Extras/GrowlMail/GrowlMail.m	Fri Jun 05 21:49:20 2009 -0700
     2.2 +++ b/Extras/GrowlMail/GrowlMail.m	Sat Jun 06 21:30:17 2009 -0700
     2.3 @@ -34,25 +34,8 @@
     2.4  //
     2.5  
     2.6  #import "GrowlMail.h"
     2.7 -#import "Message+GrowlMail.h"
     2.8  
     2.9 -#import "MailHeaders.h"
    2.10 -#import "MessageFrameworkHeaders.h"
    2.11 -#import <objc/objc-class.h>
    2.12 -
    2.13 -typedef enum {
    2.14 -	MODE_AUTO = 0,
    2.15 -	MODE_SINGLE = 1,
    2.16 -	MODE_SUMMARY = 2
    2.17 -} GrowlMailModeType;
    2.18 -
    2.19 -#define AUTO_THRESHOLD	10
    2.20 -
    2.21 -#define	MAX_NOTIFICATION_THREADS	5
    2.22 -
    2.23 -static int	activeNotificationThreads = 0;
    2.24 -
    2.25 -//#define GROWL_MAIL_DEBUG
    2.26 +#import "GrowlMailNotifier.h"
    2.27  
    2.28  NSBundle *GMGetGrowlMailBundle(void) {
    2.29  	return [NSBundle bundleForClass:[GrowlMail class]];
    2.30 @@ -60,25 +43,6 @@
    2.31  
    2.32  @implementation GrowlMail
    2.33  
    2.34 -static int messageCopies = 0;
    2.35 -
    2.36 -#pragma mark Panic buttons
    2.37 -
    2.38 -//The purpose of this method is to shut down GrowlMail completely: we should not be notified of any messages, nor notify the user of any messages, after this message is called.
    2.39 -- (void) shutDownGrowlMail {
    2.40 -	[[NSNotificationCenter defaultCenter] removeObserver:self];
    2.41 -	[GrowlApplicationBridge setGrowlDelegate:nil];
    2.42 -}
    2.43 -
    2.44 -//This is a suicide pill. GrowlMail sends itself this message any time it detects a change in Mail's implementation, such as a missing method or an object of the wrong class.
    2.45 -- (void) shutDownGrowlMailAndWarn:(NSString *)specificWarning {
    2.46 -	NSLog(NSLocalizedString(@"WARNING: Mail is not behaving in the way that GrowlMail expects. This is probably because GrowlMail is incompatible with the version of Mail you're using. GrowlMail will now turn itself off. Please check the Growl website for a new version. If you're a programmer and want to debug this error, run gdb, load Mail, set a breakpoint on %s, and run.", /*comment*/ nil), __PRETTY_FUNCTION__);
    2.47 -	if (specificWarning)
    2.48 -		NSLog(@"Furthermore, the caller provided a more specific message: %@", specificWarning);
    2.49 -
    2.50 -	[self shutDownGrowlMail];
    2.51 -}
    2.52 -
    2.53  #pragma mark Boring bookkeeping stuff
    2.54  
    2.55  + (void) initialize {
    2.56 @@ -90,17 +54,6 @@
    2.57  
    2.58  	[GrowlMail registerBundle];
    2.59  
    2.60 -	NSNumber *automatic = [NSNumber numberWithInt:MODE_AUTO];
    2.61 -	NSDictionary *defaultsDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
    2.62 -		@"(%account) %sender",         @"GMTitleFormat",
    2.63 -		@"%subject\n%body",            @"GMDescriptionFormat",
    2.64 -		automatic,                     @"GMSummaryMode",
    2.65 -		[NSNumber numberWithBool:YES], @"GMEnableGrowlMailBundle",
    2.66 -		[NSNumber numberWithBool:NO],  @"GMInboxOnly",
    2.67 -		nil];
    2.68 -	[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
    2.69 -	[defaultsDictionary release];
    2.70 -
    2.71  	NSLog(@"Loaded GrowlMail %@", [GMGetGrowlMailBundle() objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]);
    2.72  }
    2.73  
    2.74 @@ -124,10 +77,12 @@
    2.75  		NSBundle *growlBundle = [NSBundle bundleWithPath:growlBundlePath];
    2.76  		if (growlBundle) {
    2.77  			if ([growlBundle load]) {
    2.78 -				// Register ourselves as a Growl delegate
    2.79 -				[GrowlApplicationBridge setGrowlDelegate:self];
    2.80 +				if ([GrowlApplicationBridge respondsToSelector:@selector(frameworkInfoDictionary)]) {
    2.81 +					//Create or obtain our singleton notifier instance.
    2.82 +					notifier = [[GrowlMailNotifier alloc] init];
    2.83 +					if (!notifier)
    2.84 +						NSLog(@"Could not initialize GrowlMail notifier object");
    2.85  
    2.86 -				if ([GrowlApplicationBridge respondsToSelector:@selector(frameworkInfoDictionary)]) {
    2.87  					NSDictionary *infoDictionary = [GrowlApplicationBridge frameworkInfoDictionary];
    2.88  					NSLog(@"Using Growl.framework %@ (%@)",
    2.89  						  [infoDictionary objectForKey:@"CFBundleShortVersionString"],
    2.90 @@ -136,28 +91,6 @@
    2.91  					NSLog(@"Using a version of Growl.framework older than 1.1. One of the other installed Mail plugins should be updated to Growl.framework 1.1 or later.");
    2.92  				}
    2.93  			}
    2.94 -
    2.95 -			[[NSNotificationCenter defaultCenter] addObserver:self
    2.96 -													 selector:@selector(messageStoreDidAddMessages:)
    2.97 -														 name:@"MessageStoreMessagesAdded_inMainThread_"
    2.98 -													   object:nil];
    2.99 -			[[NSNotificationCenter defaultCenter] addObserver:self
   2.100 -													 selector:@selector(monitoredActivityStarted:)
   2.101 -														 name:@"MonitoredActivityStarted_inMainThread_"
   2.102 -													   object:nil];
   2.103 -			[[NSNotificationCenter defaultCenter] addObserver:self
   2.104 -													 selector:@selector(monitoredActivityEnded:)
   2.105 -														 name:@"MonitoredActivityEnded_inMainThread_"
   2.106 -													   object:nil];
   2.107 -			
   2.108 -#ifdef GROWL_MAIL_DEBUG
   2.109 -			/*
   2.110 -			[[NSNotificationCenter defaultCenter] addObserver:self
   2.111 -													 selector:@selector(showAllNotifications:)
   2.112 -														 name:nil object:nil];
   2.113 -			 */
   2.114 -#endif
   2.115 -			
   2.116  		} else {
   2.117  			NSLog(@"Could not load Growl.framework, GrowlMail disabled");
   2.118  		}
   2.119 @@ -166,379 +99,10 @@
   2.120  	return self;
   2.121  }
   2.122  
   2.123 -- (void)showAllNotifications:(NSNotification *)notification
   2.124 -{
   2.125 -	if (([[notification name] rangeOfString:@"NSWindow"].location == NSNotFound) &&
   2.126 -		([[notification name] rangeOfString:@"NSMouse"].location == NSNotFound) &&
   2.127 -		([[notification name] rangeOfString:@"_NSThread"].location == NSNotFound)) {
   2.128 -		NSLog(@"%@", notification);
   2.129 -	}
   2.130 -}
   2.131 -
   2.132 -- (void)monitoredActivityStarted:(NSNotification *)notification
   2.133 -{
   2.134 -	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   2.135 -		messageCopies++;
   2.136 -#ifdef GROWL_MAIL_DEBUG
   2.137 -		NSLog(@"Copying a message: messageCopies is now %i", messageCopies);
   2.138 -#endif
   2.139 -		if (messageCopies <= 0)
   2.140 -			[self shutDownGrowlMailAndWarn:@"Number of message-copying operations overflowed. How on earth did you accomplish starting more than 2 billion copying operations at a time?!"];
   2.141 -	}
   2.142 -}
   2.143 -
   2.144 -- (void)monitoredActivityEnded:(NSNotification *)notification
   2.145 -{
   2.146 -	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   2.147 -		if (messageCopies <= 0)
   2.148 -			[self shutDownGrowlMailAndWarn:@"Number of message-copying operations went below 0. It is not possible to have a negative number of copying operations!"];
   2.149 -		messageCopies--;
   2.150 -#ifdef GROWL_MAIL_DEBUG
   2.151 -		NSLog(@"Finished copying a message: messageCopies is now %i", messageCopies);
   2.152 -#endif
   2.153 -	}
   2.154 -}
   2.155 -
   2.156  - (void) dealloc {
   2.157 -	[self shutDownGrowlMail];
   2.158 -	[[NSNotificationCenter defaultCenter] removeObserver:self];
   2.159 +	[notifier release];
   2.160  
   2.161  	[super dealloc];
   2.162  }
   2.163  
   2.164 -#pragma mark GrowlApplicationBridge delegate methods
   2.165 -
   2.166 -- (NSString *) applicationNameForGrowl {
   2.167 -	return @"GrowlMail";
   2.168 -}
   2.169 -
   2.170 -- (NSImage *) applicationIconForGrowl {
   2.171 -	return [NSImage imageNamed:@"NSApplicationIcon"];
   2.172 -}
   2.173 -
   2.174 -- (void) growlNotificationWasClicked:(NSString *)clickContext {
   2.175 -	if ([clickContext length]) {
   2.176 -		//Make sure we have all the methods we need.
   2.177 -		if (!class_getClassMethod([Library class], @selector(messageWithMessageID:)))
   2.178 -			[self shutDownGrowlMailAndWarn:@"Library does not respond to +messageWithMessageID:"];
   2.179 -		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(initForViewingMessage:showAllHeaders:viewingState:fromDefaults:)))
   2.180 -			[self shutDownGrowlMailAndWarn:@"SingleMessageViewer does not respond to -initForViewingMessage:showAllHeaders:viewingState:fromDefaults:"];
   2.181 -		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(showAndMakeKey:)))
   2.182 -			[self shutDownGrowlMailAndWarn:@"SingleMessageViewer does not respond to -showAndMakeKey:"];
   2.183 -
   2.184 -		Message *message = [Library messageWithMessageID:clickContext];
   2.185 -		MessageViewingState *viewingState = [[MessageViewingState alloc] init];
   2.186 -		SingleMessageViewer *messageViewer = [[SingleMessageViewer alloc] initForViewingMessage:message showAllHeaders:NO viewingState:viewingState fromDefaults:NO];
   2.187 -		[viewingState release];
   2.188 -		[messageViewer showAndMakeKey:YES];
   2.189 -		[messageViewer release];
   2.190 -		[Library markMessageAsViewed:message];
   2.191 -	}
   2.192 -	[NSApp activateIgnoringOtherApps:YES];
   2.193 -}
   2.194 -
   2.195 -- (NSDictionary *) registrationDictionaryForGrowl {
   2.196 -	// Register our ticket with Growl
   2.197 -	NSArray *allowedNotifications = [NSArray arrayWithObjects:
   2.198 -		NEW_MAIL_NOTIFICATION,
   2.199 -		NEW_JUNK_MAIL_NOTIFICATION,
   2.200 -		NEW_NOTE_NOTIFICATION,
   2.201 -		nil];
   2.202 -	NSDictionary *humanReadableNames = [NSDictionary dictionaryWithObjectsAndKeys:
   2.203 -										NSLocalizedStringFromTableInBundle(@"New mail", nil, GMGetGrowlMailBundle(), ""), NEW_MAIL_NOTIFICATION,
   2.204 -										NSLocalizedStringFromTableInBundle(@"New junk mail", nil, GMGetGrowlMailBundle(), ""), NEW_JUNK_MAIL_NOTIFICATION,
   2.205 -										NSLocalizedStringFromTableInBundle(@"New note", nil, GMGetGrowlMailBundle(), ""), NEW_NOTE_NOTIFICATION,
   2.206 -										nil];
   2.207 -	NSArray *defaultNotifications = [NSArray arrayWithObject:NEW_MAIL_NOTIFICATION];
   2.208 -
   2.209 -	NSDictionary *ticket = [NSDictionary dictionaryWithObjectsAndKeys:
   2.210 -		allowedNotifications, GROWL_NOTIFICATIONS_ALL,
   2.211 -		defaultNotifications, GROWL_NOTIFICATIONS_DEFAULT,
   2.212 -		humanReadableNames, GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
   2.213 -		nil];
   2.214 -#ifdef GROWL_MAIL_DEBUG
   2.215 -	NSLog(@"%s: Returning Growl dictionary %@", __PRETTY_FUNCTION__, ticket);
   2.216 -#endif
   2.217 -
   2.218 -	return ticket;
   2.219 -}
   2.220 -
   2.221 -#pragma mark Mail notification handlers
   2.222 -
   2.223 -+ (void)showNotificationForMessage:(Message *)message
   2.224 -{
   2.225 -	if (activeNotificationThreads < MAX_NOTIFICATION_THREADS) { 
   2.226 -		activeNotificationThreads++;
   2.227 -		
   2.228 -		/* Why use a thread?
   2.229 -		 *
   2.230 -		 * If we want the message body, it may not be immediately available.
   2.231 -		 * It can be retrieved without blocking if it's available, which we initially try.
   2.232 -		 * However, if we really, really want it, we may have to request it in a blocking fashion:
   2.233 -		 *		for example, if the user doesn't read the message and doesn't have Mail set to download it automatically,
   2.234 -		 *		we'll never get it without blocking.
   2.235 -		 *
   2.236 -		 * Blocking the main thread is, of course, out of the question.
   2.237 -		 *
   2.238 -		 * We're making some assumptions about Mail's internals, but the fact that notifications are posted on auxiliary threads
   2.239 -		 * and then again with a _inMainThread_ suffix on the main thread indicates that threads are being used for mail access elsewhere.
   2.240 -		 */
   2.241 -		[NSThread detachNewThreadSelector:@selector(GMShowNotificationPart1)
   2.242 -								 toTarget:message
   2.243 -							   withObject:nil];
   2.244 -	} else {
   2.245 -		[self performSelector:@selector(showNotificationForMessage:)
   2.246 -				   withObject:message
   2.247 -				   afterDelay:2.0];
   2.248 -	}
   2.249 -}
   2.250 -
   2.251 -+ (void)didFinishNotificationForMessage:(Message *)message
   2.252 -{
   2.253 -#pragma unused(message)
   2.254 -	activeNotificationThreads--;	
   2.255 -}
   2.256 -
   2.257 -- (void)messageStoreDidAddMessages:(NSNotification *)notification {
   2.258 -	if (!GMIsEnabled()) return;
   2.259 -
   2.260 -#ifdef GROWL_MAIL_DEBUG
   2.261 -	NSLog(@"%s called", __PRETTY_FUNCTION__);
   2.262 -#endif
   2.263 -	
   2.264 -	if (messageCopies) {
   2.265 -#ifdef GROWL_MAIL_DEBUG
   2.266 -		NSLog(@"Ignoring because %i message copies are in process", messageCopies);
   2.267 -#endif
   2.268 -		return;
   2.269 -	}
   2.270 -
   2.271 -	Library *store = [notification object];
   2.272 -	if (!store) {
   2.273 -		[self shutDownGrowlMailAndWarn:[NSString stringWithFormat:@"'%@' notification has no object", [notification name]]];
   2.274 -	}
   2.275 -	if ([store isKindOfClass:[LibraryStore class]]) {
   2.276 -		//As of Tiger, this is normal; this notification is posted a couple times (perhaps once per inbox) with a LibraryStore object.
   2.277 -		//This is not the notification we're looking for; we don't need to see its papers. We will move along now.
   2.278 -		return;
   2.279 -	}
   2.280 -	//We don't actually use the store. We only retrieve it and examine it at all because we know we don't want the one with a LibraryStore as its object.
   2.281 -	//The rest of the handler should be able to work just fine without proving anything else about the store, since it doesn't use the store.
   2.282 -
   2.283 -	NSDictionary *userInfo = [notification userInfo];
   2.284 -	if (!userInfo) [self shutDownGrowlMailAndWarn:@"Notification had no userInfo"];
   2.285 -
   2.286 -	NSArray *mailboxes = [userInfo objectForKey:@"mailboxes"];
   2.287 -#ifdef GROWL_MAIL_DEBUG
   2.288 -	NSLog(@"%s: Adding messages to mailboxes %@", __PRETTY_FUNCTION__, mailboxes);
   2.289 -#endif
   2.290 -
   2.291 -	//As of Tiger, it's normal for about half of these notifications to not have any mailboxes. We simply ignore the notification in this case.
   2.292 -	if (!(mailboxes && [mailboxes count])) return;
   2.293 -
   2.294 -	//Ignore a notification if we're ignoring all of the mailboxes involved.
   2.295 -	Class MailAccount_class = [MailAccount class];
   2.296 -	if (!class_getClassMethod(MailAccount_class, @selector(draftMailboxUids)))
   2.297 -		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +draftMailboxUids"];
   2.298 -	if (!class_getClassMethod(MailAccount_class, @selector(outboxMailboxUids)))
   2.299 -		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +outboxMailboxUids"];
   2.300 -	if (!class_getClassMethod(MailAccount_class, @selector(sentMessagesMailboxUids)))
   2.301 -		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +sentMessagesMailboxUids"];
   2.302 -	if (!class_getClassMethod(MailAccount_class, @selector(trashMailboxUids)))
   2.303 -		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +trashMailboxUids"];
   2.304 -	//We need this method to support the Inbox Only preference.
   2.305 -	if (!class_getClassMethod(MailAccount_class, @selector(inboxMailboxUids)))
   2.306 -		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +inboxMailboxUids"];
   2.307 -
   2.308 -	//Ignore messages being written.
   2.309 -	NSMutableSet *mailboxesToIgnore = [NSMutableSet setWithArray:[MailAccount draftMailboxUids]];
   2.310 -	//Ignore messages being sent.
   2.311 -	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount outboxMailboxUids]]];
   2.312 -	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount sentMessagesMailboxUids]]];
   2.313 -	//Ignore messages being deleted.
   2.314 -	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount trashMailboxUids]]];
   2.315 -
   2.316 -	NSSet *mailboxesSet = [NSSet setWithArray:mailboxes];
   2.317 -	NSMutableSet *mailboxesNotIgnored = [[mailboxesSet mutableCopy] autorelease];
   2.318 -	[mailboxesNotIgnored minusSet:mailboxesToIgnore];
   2.319 -	if ([mailboxesNotIgnored count] == 0U)
   2.320 -		return;
   2.321 -
   2.322 -	NSArray *messages = [userInfo objectForKey:@"messages"];
   2.323 -	if (!messages) [self shutDownGrowlMailAndWarn:@"Notification's userInfo has no messages"];
   2.324 -	
   2.325 -#ifdef GROWL_MAIL_DEBUG
   2.326 -	NSLog(@"%s: Mail added messages [1] to mailboxes [2].\n[1]: %@\n[2]: %@", __PRETTY_FUNCTION__, messages, mailboxes);
   2.327 -#endif
   2.328 -	
   2.329 -	unsigned count = [messages count];
   2.330 -
   2.331 -	int summaryMode = GMSummaryMode();
   2.332 -	if (summaryMode == MODE_AUTO) {
   2.333 -		if (count >= AUTO_THRESHOLD)
   2.334 -			summaryMode = MODE_SUMMARY;
   2.335 -		else
   2.336 -			summaryMode = MODE_SINGLE;
   2.337 -	}
   2.338 -
   2.339 -#ifdef GROWL_MAIL_DEBUG
   2.340 -	NSLog(@"Got %i new messages. Summary mode was %i and is now %i", count, GMSummaryMode(), summaryMode);
   2.341 -#endif
   2.342 -
   2.343 -	Class Message_class = [Message class];
   2.344 -
   2.345 -	switch (summaryMode) {
   2.346 -		default:
   2.347 -		case MODE_SINGLE: {
   2.348 -			NSEnumerator *messagesEnum = [messages objectEnumerator];
   2.349 -			Message *message;
   2.350 -			while ((message = [messagesEnum nextObject])) {
   2.351 -				MailboxUid *mailbox = [message mailbox];
   2.352 -				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   2.353 -				if (GMInboxOnly() && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   2.354 -					continue;
   2.355 -
   2.356 -				MailAccount *account = [mailbox account];
   2.357 -				if (![self isAccountEnabled:account])
   2.358 -					continue;
   2.359 -
   2.360 -				if (![message isKindOfClass:Message_class])
   2.361 -					[self shutDownGrowlMailAndWarn:[NSString stringWithFormat:@"Message in notification was not a Message; it is %@", message]];
   2.362 -
   2.363 -				if (![message respondsToSelector:@selector(isRead)] || ![message isRead]) {
   2.364 -					/* Don't display read messages */
   2.365 -					[[self class] showNotificationForMessage:message];
   2.366 -				}
   2.367 -			}
   2.368 -			break;
   2.369 -		}
   2.370 -		case MODE_SUMMARY: {
   2.371 -			if (!class_getClassMethod([MailAccount class], @selector(mailAccounts)))
   2.372 -				[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +mailAccounts"];
   2.373 -			if (!class_getInstanceMethod(Message_class, @selector(mailbox)))
   2.374 -				[self shutDownGrowlMailAndWarn:@"Message does not respond to -mailbox"];
   2.375 -
   2.376 -			NSArray *accounts = [MailAccount mailAccounts];
   2.377 -			unsigned accountsCount = [accounts count];
   2.378 -			NSCountedSet *accountSummary = [NSCountedSet setWithCapacity:accountsCount];
   2.379 -			NSCountedSet *accountJunkSummary = [NSCountedSet setWithCapacity:accountsCount];
   2.380 -			NSEnumerator *messagesEnum = [messages objectEnumerator];
   2.381 -			NSArray *junkMailboxUids = [MailAccount junkMailboxUids];
   2.382 -			Message *message;
   2.383 -			while ((message = [messagesEnum nextObject])) {
   2.384 -				MailboxUid *mailbox = [message mailbox];
   2.385 -				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   2.386 -				if (GMInboxOnly() && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   2.387 -					continue;
   2.388 -
   2.389 -				MailAccount *account = [mailbox account];
   2.390 -				if (![self isAccountEnabled:account])
   2.391 -					continue;
   2.392 -
   2.393 -				if (([message isJunk]) || [junkMailboxUids containsObject:[message mailbox]])
   2.394 -					[accountJunkSummary addObject:account];
   2.395 -				else
   2.396 -					[accountSummary addObject:account];
   2.397 -			}
   2.398 -			NSString *title = NSLocalizedStringFromTableInBundle(@"New mail", NULL, GMGetGrowlMailBundle(), "");
   2.399 -			NSString *titleJunk = NSLocalizedStringFromTableInBundle(@"New junk mail", NULL, GMGetGrowlMailBundle(), "");
   2.400 -			NSString *description;
   2.401 -
   2.402 -			MailAccount *account;
   2.403 -
   2.404 -			NSEnumerator *accountSummaryEnum = [accountSummary objectEnumerator];
   2.405 -			while ((account = [accountSummaryEnum nextObject])) {
   2.406 -				if (![self isAccountEnabled:account])
   2.407 -					continue;
   2.408 -
   2.409 -				unsigned summaryCount = [accountSummary countForObject:account];
   2.410 -				if (summaryCount) {
   2.411 -					if (summaryCount == 1) {
   2.412 -						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   2.413 -					} else {
   2.414 -						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   2.415 -					}
   2.416 -					[GrowlApplicationBridge notifyWithTitle:title
   2.417 -												description:description
   2.418 -										   notificationName:NEW_MAIL_NOTIFICATION
   2.419 -												   iconData:nil
   2.420 -												   priority:0
   2.421 -												   isSticky:NO
   2.422 -											   clickContext:@""];	// non-nil click context
   2.423 -				}
   2.424 -			}
   2.425 -
   2.426 -			NSEnumerator *accountJunkSummaryEnum = [accountJunkSummary objectEnumerator];
   2.427 -			while ((account = [accountJunkSummaryEnum nextObject])) {
   2.428 -				if (![self isAccountEnabled:account])
   2.429 -					continue;
   2.430 -
   2.431 -				unsigned summaryCount = [accountJunkSummary countForObject:account];
   2.432 -				if (summaryCount) {
   2.433 -					if (summaryCount == 1) {
   2.434 -						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   2.435 -					} else {
   2.436 -						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   2.437 -					}					
   2.438 -					[GrowlApplicationBridge notifyWithTitle:titleJunk
   2.439 -												description:description
   2.440 -										   notificationName:NEW_JUNK_MAIL_NOTIFICATION
   2.441 -												   iconData:nil
   2.442 -												   priority:0
   2.443 -												   isSticky:NO
   2.444 -											   clickContext:@""];	// non-nil click context
   2.445 -				}
   2.446 -			}
   2.447 -			break;
   2.448 -		}
   2.449 -	}
   2.450 -}
   2.451 -
   2.452 -#pragma mark Preferences
   2.453 -
   2.454 -- (BOOL) isAccountEnabled:(MailAccount *)account {
   2.455 -	BOOL isEnabled = YES;
   2.456 -	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   2.457 -	if (accountSettings) {
   2.458 -		NSNumber *value = [accountSettings objectForKey:[account path]];
   2.459 -		if (value)
   2.460 -			isEnabled = [value boolValue];
   2.461 -	}
   2.462 -	return isEnabled;
   2.463 -}
   2.464 -
   2.465 -- (void) setAccount:(MailAccount *)account enabled:(BOOL)yesOrNo {
   2.466 -	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   2.467 -	NSMutableDictionary *newSettings = [[accountSettings mutableCopy] autorelease];
   2.468 -	if (!newSettings)
   2.469 -		newSettings = [NSMutableDictionary dictionaryWithCapacity:1U];
   2.470 -	[newSettings setObject:[NSNumber numberWithBool:yesOrNo] forKey:[account path]];
   2.471 -	[[NSUserDefaults standardUserDefaults] setObject:newSettings forKey:@"GMAccounts"];
   2.472 -}
   2.473 -
   2.474  @end
   2.475 -
   2.476 -BOOL GMIsEnabled(void) {
   2.477 -	NSNumber *enabledNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMEnableGrowlMailBundle"];
   2.478 -	return enabledNum ? [enabledNum boolValue] : YES;
   2.479 -}
   2.480 -
   2.481 -int GMSummaryMode(void) {
   2.482 -	NSNumber *summaryModeNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMSummaryMode"];
   2.483 -	return summaryModeNum ? [summaryModeNum intValue] : MODE_AUTO;
   2.484 -}
   2.485 -
   2.486 -BOOL GMInboxOnly(void) {
   2.487 -	NSNumber *inboxOnlyNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMInboxOnly"];
   2.488 -	return inboxOnlyNum ? [inboxOnlyNum boolValue] : YES;
   2.489 -}
   2.490 -
   2.491 -NSString *GMTitleFormatString(void) {
   2.492 -	NSString *titleFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMTitleFormat"];
   2.493 -	return titleFormat ? titleFormat : @"(%account) %sender";
   2.494 -}
   2.495 -
   2.496 -NSString *GMDescriptionFormatString(void) {
   2.497 -	NSString *descriptionFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMDescriptionFormat"];
   2.498 -	return descriptionFormat ? descriptionFormat : @"%subject\n%body";
   2.499 -}
     3.1 --- a/Extras/GrowlMail/GrowlMail.xcodeproj/project.pbxproj	Fri Jun 05 21:49:20 2009 -0700
     3.2 +++ b/Extras/GrowlMail/GrowlMail.xcodeproj/project.pbxproj	Sat Jun 06 21:30:17 2009 -0700
     3.3 @@ -22,6 +22,7 @@
     3.4  /* End PBXAggregateTarget section */
     3.5  
     3.6  /* Begin PBXBuildFile section */
     3.7 +		31D72A870FB6DE5800C29D55 /* GrowlMailNotifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 31D72A860FB6DE5800C29D55 /* GrowlMailNotifier.m */; };
     3.8  		4B2E729606B5776B00386BF2 /* Message.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B2E729506B5776B00386BF2 /* Message.framework */; };
     3.9  		4B7E5EA806B6AC1700137892 /* GrowlMail.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E5EA306B6AC1700137892 /* GrowlMail.m */; };
    3.10  		8D5B49B0048680CD000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C167DFE841241C02AAC07 /* InfoPlist.strings */; };
    3.11 @@ -183,6 +184,8 @@
    3.12  		089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
    3.13  		089C167FFE841241C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
    3.14  		1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
    3.15 +		31D72A850FB6DE5800C29D55 /* GrowlMailNotifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GrowlMailNotifier.h; sourceTree = "<group>"; };
    3.16 +		31D72A860FB6DE5800C29D55 /* GrowlMailNotifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GrowlMailNotifier.m; sourceTree = "<group>"; };
    3.17  		34CD982C0C87C8AA0040C77F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = "<group>"; };
    3.18  		34CD982D0C87C8AA0040C77F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
    3.19  		34CD98310C87C8C60040C77F /* sv */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = sv; path = sv.lproj/GrowlMailPreferencesPanel.nib; sourceTree = "<group>"; };
    3.20 @@ -288,6 +291,8 @@
    3.21  				95BF5481072FB08000834811 /* Message+GrowlMail.m */,
    3.22  				4B7E5EA206B6AC1700137892 /* GrowlMail.h */,
    3.23  				4B7E5EA306B6AC1700137892 /* GrowlMail.m */,
    3.24 +				31D72A850FB6DE5800C29D55 /* GrowlMailNotifier.h */,
    3.25 +				31D72A860FB6DE5800C29D55 /* GrowlMailNotifier.m */,
    3.26  			);
    3.27  			name = Classes;
    3.28  			sourceTree = "<group>";
    3.29 @@ -566,6 +571,7 @@
    3.30  				95BF5483072FB08000834811 /* Message+GrowlMail.m in Sources */,
    3.31  				9553469F0732BD4800D12875 /* GrowlMailPreferencesModule.m in Sources */,
    3.32  				955348600732F93600D12875 /* GrowlMailPreferences.m in Sources */,
    3.33 +				31D72A870FB6DE5800C29D55 /* GrowlMailNotifier.m in Sources */,
    3.34  			);
    3.35  			runOnlyForDeploymentPostprocessing = 0;
    3.36  		};
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/Extras/GrowlMail/GrowlMailNotifier.h	Sat Jun 06 21:30:17 2009 -0700
     4.3 @@ -0,0 +1,97 @@
     4.4 +//
     4.5 +//  GrowlMailNotifier.h
     4.6 +//  GrowlMail
     4.7 +//
     4.8 +//  Created by Peter Hosey on 2009-05-10.
     4.9 +//  Copyright 2009 Peter Hosey. All rights reserved.
    4.10 +//
    4.11 +
    4.12 +#import <Cocoa/Cocoa.h>
    4.13 +#import <Growl/Growl.h>
    4.14 +#import "MailHeaders.h"
    4.15 +
    4.16 +#define NEW_MAIL_NOTIFICATION		@"New mail"
    4.17 +#define NEW_JUNK_MAIL_NOTIFICATION	@"New junk mail"
    4.18 +#define NEW_NOTE_NOTIFICATION		@"New note"
    4.19 +
    4.20 +/*!	@brief	Summary mode constants
    4.21 + *
    4.22 + *	GrowlMail can post two kinds of notifications: One notification for every message the user receives, or a summary notification that lists only the number of messages the user received on a single account.
    4.23 + *
    4.24 + *	@par	The GMSummaryMode user default contains a number that specifies how GrowlMail should post notifications: always as single-message notifications, always as a summary, or automatically chosen based on number of messages added to the store in a single operation.
    4.25 + */
    4.26 +enum GrowlMailSummaryMode {
    4.27 +	/*!	@brief	Automatically use summary mode or not based on how many messages the user receives within a span of time
    4.28 +	 */
    4.29 +	GrowlMailSummaryModeAutomatic = 0,
    4.30 +	/*!	@brief	Always post one notification per message
    4.31 +	 */
    4.32 +	GrowlMailSummaryModeDisabled = 1,
    4.33 +	/*!	@brief	Always post a summary notification per account
    4.34 +	 */
    4.35 +	GrowlMailSummaryModeAlways = 2
    4.36 +};
    4.37 +//XXX 64-bit: Change this to NSInteger
    4.38 +typedef int GrowlMailSummaryMode;
    4.39 +
    4.40 +/*!	@brief	Object that posts GrowlMail notifications
    4.41 + *
    4.42 + *	This is a singleton object because the current Growl API can only handle one delegate at a time.
    4.43 + */
    4.44 +@interface GrowlMailNotifier : NSObject <GrowlApplicationBridgeDelegate>
    4.45 +{
    4.46 +}
    4.47 +
    4.48 +/*!	@brief	Return the One True \c GrowlMailNotifier Instance, creating it if necessary.
    4.49 + */
    4.50 ++ (id) sharedNotifier;
    4.51 +
    4.52 +/*!	@brief	Creates or retains, then returns, the One True \c GrowlMailNotifier instance.
    4.53 + *
    4.54 + *	If the shared instance does not yet exist, this method makes the receiver the shared instance and initializes it.
    4.55 + *	If the shared instance does already exist, this method releases the receiver, then returns the shared instance.
    4.56 + *	Either way, it then returns the shared instance.
    4.57 + *
    4.58 + *	@par	This method will return \c nil if the suicide pill has previously been invoked.
    4.59 + *
    4.60 + *	@return	The One True GrowlMailNotifier instance, or \c nil.
    4.61 + */
    4.62 +- (id) init;
    4.63 +
    4.64 +- (BOOL) isEnabled;
    4.65 +- (GrowlMailSummaryMode) summaryMode;
    4.66 +/*!	@brief	Only post notifications for messages added to an account's inbox, not to other mailboxes (folders).
    4.67 + */
    4.68 +- (BOOL) inboxOnly;
    4.69 +
    4.70 +/*!	@brief	Returns the correct format string for Growl notification titles.
    4.71 + *
    4.72 + *	The returned format is only useful for single-message notifications. Summary notifications do not use this format.
    4.73 + */
    4.74 +- (NSString *) titleFormat;
    4.75 +/*!	@brief	Returns the correct format string for Growl notification descriptions.
    4.76 + *
    4.77 + *	The returned format is only useful for single-message notifications. Summary notifications do not use this format.
    4.78 + */
    4.79 +- (NSString *) descriptionFormat;
    4.80 +
    4.81 +/*!	@brief	Return whether the given account is enabled for notifications
    4.82 + *
    4.83 + *	@return	\c YES if GrowlMail will post notifications for this account; \c NO if it won't.
    4.84 + */
    4.85 +- (BOOL) isAccountEnabled:(MailAccount *)account;
    4.86 +/*!	@brief	Change whether the given account is enabled for notifications
    4.87 + *
    4.88 + *	@param	account	The account to enable or disable.
    4.89 + *	@param	yesOrNo	If \c YES, post notifications for messages for \a account in the future; if \c NO, don't post notifications for messages for that account.
    4.90 + */
    4.91 +- (void) setAccount:(MailAccount *)account enabled:(BOOL)yesOrNo;
    4.92 +
    4.93 +- (NSString *) applicationNameForGrowl;
    4.94 +- (NSImage *) applicationIconForGrowl;
    4.95 +- (void) growlNotificationWasClicked:(NSString *)clickContext;
    4.96 +- (NSDictionary *) registrationDictionaryForGrowl;
    4.97 +
    4.98 +- (void)didFinishNotificationForMessage:(Message *)message;
    4.99 +
   4.100 +@end
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/Extras/GrowlMail/GrowlMailNotifier.m	Sat Jun 06 21:30:17 2009 -0700
     5.3 @@ -0,0 +1,485 @@
     5.4 +//
     5.5 +//  GrowlMailNotifier.m
     5.6 +//  GrowlMail
     5.7 +//
     5.8 +//  Created by Peter Hosey on 2009-05-10.
     5.9 +//  Copyright 2009 Peter Hosey. All rights reserved.
    5.10 +//
    5.11 +
    5.12 +#import "GrowlMailNotifier.h"
    5.13 +#import "GrowlMail.h"
    5.14 +#import "Message+GrowlMail.h"
    5.15 +#import <objc/objc-runtime.h>
    5.16 +
    5.17 +#import "MessageFrameworkHeaders.h"
    5.18 +
    5.19 +#define AUTO_THRESHOLD	10
    5.20 +
    5.21 +#define	MAX_NOTIFICATION_THREADS	5
    5.22 +
    5.23 +static int activeNotificationThreads = 0;
    5.24 +
    5.25 +static int messageCopies = 0;
    5.26 +
    5.27 +static GrowlMailNotifier *sharedNotifier = nil;
    5.28 +
    5.29 +static BOOL notifierEnabled = YES;
    5.30 +
    5.31 +@implementation GrowlMailNotifier
    5.32 +
    5.33 +#pragma mark Panic buttons
    5.34 +
    5.35 +//The purpose of this method is to shut down GrowlMail completely: we should not be notified of any messages, nor notify the user of any messages, after this message is called.
    5.36 +- (void) shutDownGrowlMail {
    5.37 +	[[NSNotificationCenter defaultCenter] removeObserver:self];
    5.38 +	[GrowlApplicationBridge setGrowlDelegate:nil];
    5.39 +
    5.40 +	//Prevent ourselves from re-enabling later.
    5.41 +	notifierEnabled = NO;
    5.42 +}
    5.43 +
    5.44 +//This is a suicide pill. GrowlMail sends itself this message any time it detects a change in Mail's implementation, such as a missing method or an object of the wrong class.
    5.45 +- (void) shutDownGrowlMailAndWarn:(NSString *)specificWarning {
    5.46 +	NSLog(NSLocalizedString(@"WARNING: Mail is not behaving in the way that GrowlMail expects. This is probably because GrowlMail is incompatible with the version of Mail you're using. GrowlMail will now turn itself off. Please check the Growl website for a new version. If you're a programmer and want to debug this error, run gdb, load Mail, set a breakpoint on %s, and run.", /*comment*/ nil), __PRETTY_FUNCTION__);
    5.47 +	if (specificWarning)
    5.48 +		NSLog(@"Furthermore, the caller provided a more specific message: %@", specificWarning);
    5.49 +
    5.50 +	[self shutDownGrowlMail];
    5.51 +}
    5.52 +
    5.53 +#pragma mark The circle of life
    5.54 +
    5.55 ++ (id) sharedNotifier {
    5.56 +	if (!sharedNotifier) {
    5.57 +		//-init and -dealloc will each assign to sharedNotifier.
    5.58 +		[[[GrowlMailNotifier alloc] init] autorelease];
    5.59 +	}
    5.60 +	return sharedNotifier;
    5.61 +}
    5.62 +
    5.63 +- (id) init {
    5.64 +	if (sharedNotifier) {
    5.65 +		[self release];
    5.66 +		return [sharedNotifier retain];
    5.67 +	}
    5.68 +
    5.69 +	//No shared notifier yet; someone is trying to create one. If we previously disabled ourselves, abort this attempt.
    5.70 +	if (!notifierEnabled) {
    5.71 +		[self release];
    5.72 +		return nil;
    5.73 +	}
    5.74 +
    5.75 +	if((self = [super init])) {
    5.76 +		NSNumber *automatic = [NSNumber numberWithInt:GrowlMailSummaryModeAutomatic];
    5.77 +		NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    5.78 +			@"(%account) %sender",         @"GMTitleFormat",
    5.79 +			@"%subject\n%body",            @"GMDescriptionFormat",
    5.80 +			automatic,                     @"GMSummaryMode",
    5.81 +			[NSNumber numberWithBool:YES], @"GMEnableGrowlMailBundle",
    5.82 +			[NSNumber numberWithBool:NO],  @"GMInboxOnly",
    5.83 +			nil];
    5.84 +		[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
    5.85 +
    5.86 +		[GrowlApplicationBridge setGrowlDelegate:self];
    5.87 +
    5.88 +		[[NSNotificationCenter defaultCenter] addObserver:self
    5.89 +												 selector:@selector(messageStoreDidAddMessages:)
    5.90 +													 name:@"MessageStoreMessagesAdded_inMainThread_"
    5.91 +												   object:nil];
    5.92 +		[[NSNotificationCenter defaultCenter] addObserver:self
    5.93 +												 selector:@selector(monitoredActivityStarted:)
    5.94 +													 name:@"MonitoredActivityStarted_inMainThread_"
    5.95 +												   object:nil];
    5.96 +		[[NSNotificationCenter defaultCenter] addObserver:self
    5.97 +												 selector:@selector(monitoredActivityEnded:)
    5.98 +													 name:@"MonitoredActivityEnded_inMainThread_"
    5.99 +												   object:nil];
   5.100 +		
   5.101 +#ifdef GROWL_MAIL_DEBUG
   5.102 +		/*
   5.103 +		[[NSNotificationCenter defaultCenter] addObserver:self
   5.104 +												 selector:@selector(showAllNotifications:)
   5.105 +													 name:nil object:nil];
   5.106 +		 */
   5.107 +#endif
   5.108 +		sharedNotifier = self;
   5.109 +	}
   5.110 +	return self;
   5.111 +}
   5.112 +
   5.113 +- (void) dealloc {
   5.114 +	[self shutDownGrowlMail];
   5.115 +	[[NSNotificationCenter defaultCenter] removeObserver:self];
   5.116 +	sharedNotifier = nil;
   5.117 +
   5.118 +	[super dealloc];
   5.119 +}
   5.120 +
   5.121 +#pragma mark GrowlApplicationBridge delegate methods
   5.122 +
   5.123 +- (NSString *) applicationNameForGrowl {
   5.124 +	return @"GrowlMail";
   5.125 +}
   5.126 +
   5.127 +- (NSImage *) applicationIconForGrowl {
   5.128 +	return [NSImage imageNamed:@"NSApplicationIcon"];
   5.129 +}
   5.130 +
   5.131 +- (void) growlNotificationWasClicked:(NSString *)clickContext {
   5.132 +	if ([clickContext length]) {
   5.133 +		//Make sure we have all the methods we need.
   5.134 +		if (!class_getClassMethod([Library class], @selector(messageWithMessageID:)))
   5.135 +			[self shutDownGrowlMailAndWarn:@"Library does not respond to +messageWithMessageID:"];
   5.136 +		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(initForViewingMessage:showAllHeaders:viewingState:fromDefaults:)))
   5.137 +			[self shutDownGrowlMailAndWarn:@"SingleMessageViewer does not respond to -initForViewingMessage:showAllHeaders:viewingState:fromDefaults:"];
   5.138 +		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(showAndMakeKey:)))
   5.139 +			[self shutDownGrowlMailAndWarn:@"SingleMessageViewer does not respond to -showAndMakeKey:"];
   5.140 +
   5.141 +		Message *message = [Library messageWithMessageID:clickContext];
   5.142 +		MessageViewingState *viewingState = [[MessageViewingState alloc] init];
   5.143 +		SingleMessageViewer *messageViewer = [[SingleMessageViewer alloc] initForViewingMessage:message showAllHeaders:NO viewingState:viewingState fromDefaults:NO];
   5.144 +		[viewingState release];
   5.145 +		[messageViewer showAndMakeKey:YES];
   5.146 +		[messageViewer release];
   5.147 +		[Library markMessageAsViewed:message];
   5.148 +	}
   5.149 +	[NSApp activateIgnoringOtherApps:YES];
   5.150 +}
   5.151 +
   5.152 +- (NSDictionary *) registrationDictionaryForGrowl {
   5.153 +	// Register our ticket with Growl
   5.154 +	NSArray *allowedNotifications = [NSArray arrayWithObjects:
   5.155 +		NEW_MAIL_NOTIFICATION,
   5.156 +		NEW_JUNK_MAIL_NOTIFICATION,
   5.157 +		NEW_NOTE_NOTIFICATION,
   5.158 +		nil];
   5.159 +	NSDictionary *humanReadableNames = [NSDictionary dictionaryWithObjectsAndKeys:
   5.160 +										NSLocalizedStringFromTableInBundle(@"New mail", nil, GMGetGrowlMailBundle(), ""), NEW_MAIL_NOTIFICATION,
   5.161 +										NSLocalizedStringFromTableInBundle(@"New junk mail", nil, GMGetGrowlMailBundle(), ""), NEW_JUNK_MAIL_NOTIFICATION,
   5.162 +										NSLocalizedStringFromTableInBundle(@"New note", nil, GMGetGrowlMailBundle(), ""), NEW_NOTE_NOTIFICATION,
   5.163 +										nil];
   5.164 +	NSArray *defaultNotifications = [NSArray arrayWithObject:NEW_MAIL_NOTIFICATION];
   5.165 +
   5.166 +	NSDictionary *ticket = [NSDictionary dictionaryWithObjectsAndKeys:
   5.167 +		allowedNotifications, GROWL_NOTIFICATIONS_ALL,
   5.168 +		defaultNotifications, GROWL_NOTIFICATIONS_DEFAULT,
   5.169 +		humanReadableNames, GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
   5.170 +		nil];
   5.171 +#ifdef GROWL_MAIL_DEBUG
   5.172 +	NSLog(@"%s: Returning Growl dictionary %@", __PRETTY_FUNCTION__, ticket);
   5.173 +#endif
   5.174 +
   5.175 +	return ticket;
   5.176 +}
   5.177 +
   5.178 +#pragma mark Mail notification handlers
   5.179 +
   5.180 ++ (void)showNotificationForMessage:(Message *)message
   5.181 +{
   5.182 +	if (activeNotificationThreads < MAX_NOTIFICATION_THREADS) { 
   5.183 +		activeNotificationThreads++;
   5.184 +		
   5.185 +		/* Why use a thread?
   5.186 +		 *
   5.187 +		 * If we want the message body, it may not be immediately available.
   5.188 +		 * It can be retrieved without blocking if it's available, which we initially try.
   5.189 +		 * However, if we really, really want it, we may have to request it in a blocking fashion:
   5.190 +		 *		for example, if the user doesn't read the message and doesn't have Mail set to download it automatically,
   5.191 +		 *		we'll never get it without blocking.
   5.192 +		 *
   5.193 +		 * Blocking the main thread is, of course, out of the question.
   5.194 +		 *
   5.195 +		 * We're making some assumptions about Mail's internals, but the fact that notifications are posted on auxiliary threads
   5.196 +		 * and then again with a _inMainThread_ suffix on the main thread indicates that threads are being used for mail access elsewhere.
   5.197 +		 */
   5.198 +		[NSThread detachNewThreadSelector:@selector(GMShowNotificationPart1)
   5.199 +								 toTarget:message
   5.200 +							   withObject:nil];
   5.201 +	} else {
   5.202 +		[self performSelector:@selector(showNotificationForMessage:)
   5.203 +				   withObject:message
   5.204 +				   afterDelay:2.0];
   5.205 +	}
   5.206 +}
   5.207 +
   5.208 +- (void)didFinishNotificationForMessage:(Message *)message
   5.209 +{
   5.210 +#pragma unused(message)
   5.211 +	activeNotificationThreads--;	
   5.212 +}
   5.213 +
   5.214 +- (void)messageStoreDidAddMessages:(NSNotification *)notification {
   5.215 +	if (![self isEnabled]) return;
   5.216 +
   5.217 +#ifdef GROWL_MAIL_DEBUG
   5.218 +	NSLog(@"%s called", __PRETTY_FUNCTION__);
   5.219 +#endif
   5.220 +	
   5.221 +	if (messageCopies) {
   5.222 +#ifdef GROWL_MAIL_DEBUG
   5.223 +		NSLog(@"Ignoring because %i message copies are in process", messageCopies);
   5.224 +#endif
   5.225 +		return;
   5.226 +	}
   5.227 +
   5.228 +	Library *store = [notification object];
   5.229 +	if (!store) {
   5.230 +		[self shutDownGrowlMailAndWarn:[NSString stringWithFormat:@"'%@' notification has no object", [notification name]]];
   5.231 +	}
   5.232 +	if ([store isKindOfClass:[LibraryStore class]]) {
   5.233 +		//As of Tiger, this is normal; this notification is posted a couple times (perhaps once per inbox) with a LibraryStore object.
   5.234 +		//This is not the notification we're looking for; we don't need to see its papers. We will move along now.
   5.235 +		return;
   5.236 +	}
   5.237 +	//We don't actually use the store. We only retrieve it and examine it at all because we know we don't want the one with a LibraryStore as its object.
   5.238 +	//The rest of the handler should be able to work just fine without proving anything else about the store, since it doesn't use the store.
   5.239 +
   5.240 +	NSDictionary *userInfo = [notification userInfo];
   5.241 +	if (!userInfo) [self shutDownGrowlMailAndWarn:@"Notification had no userInfo"];
   5.242 +
   5.243 +	NSArray *mailboxes = [userInfo objectForKey:@"mailboxes"];
   5.244 +#ifdef GROWL_MAIL_DEBUG
   5.245 +	NSLog(@"%s: Adding messages to mailboxes %@", __PRETTY_FUNCTION__, mailboxes);
   5.246 +#endif
   5.247 +
   5.248 +	//As of Tiger, it's normal for about half of these notifications to not have any mailboxes. We simply ignore the notification in this case.
   5.249 +	if (!(mailboxes && [mailboxes count])) return;
   5.250 +
   5.251 +	//Ignore a notification if we're ignoring all of the mailboxes involved.
   5.252 +	Class MailAccount_class = [MailAccount class];
   5.253 +	if (!class_getClassMethod(MailAccount_class, @selector(draftMailboxUids)))
   5.254 +		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +draftMailboxUids"];
   5.255 +	if (!class_getClassMethod(MailAccount_class, @selector(outboxMailboxUids)))
   5.256 +		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +outboxMailboxUids"];
   5.257 +	if (!class_getClassMethod(MailAccount_class, @selector(sentMessagesMailboxUids)))
   5.258 +		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +sentMessagesMailboxUids"];
   5.259 +	if (!class_getClassMethod(MailAccount_class, @selector(trashMailboxUids)))
   5.260 +		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +trashMailboxUids"];
   5.261 +	//We need this method to support the Inbox Only preference.
   5.262 +	if (!class_getClassMethod(MailAccount_class, @selector(inboxMailboxUids)))
   5.263 +		[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +inboxMailboxUids"];
   5.264 +
   5.265 +	//Ignore messages being written.
   5.266 +	NSMutableSet *mailboxesToIgnore = [NSMutableSet setWithArray:[MailAccount draftMailboxUids]];
   5.267 +	//Ignore messages being sent.
   5.268 +	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount outboxMailboxUids]]];
   5.269 +	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount sentMessagesMailboxUids]]];
   5.270 +	//Ignore messages being deleted.
   5.271 +	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount trashMailboxUids]]];
   5.272 +
   5.273 +	NSSet *mailboxesSet = [NSSet setWithArray:mailboxes];
   5.274 +	NSMutableSet *mailboxesNotIgnored = [[mailboxesSet mutableCopy] autorelease];
   5.275 +	[mailboxesNotIgnored minusSet:mailboxesToIgnore];
   5.276 +	if ([mailboxesNotIgnored count] == 0U)
   5.277 +		return;
   5.278 +
   5.279 +	NSArray *messages = [userInfo objectForKey:@"messages"];
   5.280 +	if (!messages) [self shutDownGrowlMailAndWarn:@"Notification's userInfo has no messages"];
   5.281 +	
   5.282 +#ifdef GROWL_MAIL_DEBUG
   5.283 +	NSLog(@"%s: Mail added messages [1] to mailboxes [2].\n[1]: %@\n[2]: %@", __PRETTY_FUNCTION__, messages, mailboxes);
   5.284 +#endif
   5.285 +	
   5.286 +	unsigned count = [messages count];
   5.287 +
   5.288 +	int summaryMode = [self summaryMode];
   5.289 +	if (summaryMode == GrowlMailSummaryModeAutomatic) {
   5.290 +		if (count >= AUTO_THRESHOLD)
   5.291 +			summaryMode = GrowlMailSummaryModeAlways;
   5.292 +		else
   5.293 +			summaryMode = GrowlMailSummaryModeDisabled;
   5.294 +	}
   5.295 +
   5.296 +#ifdef GROWL_MAIL_DEBUG
   5.297 +	NSLog(@"Got %i new messages. Summary mode was %i and is now %i", count, [self summaryMode], summaryMode);
   5.298 +#endif
   5.299 +
   5.300 +	Class Message_class = [Message class];
   5.301 +
   5.302 +	switch (summaryMode) {
   5.303 +		default:
   5.304 +		case GrowlMailSummaryModeDisabled: {
   5.305 +			NSEnumerator *messagesEnum = [messages objectEnumerator];
   5.306 +			Message *message;
   5.307 +			while ((message = [messagesEnum nextObject])) {
   5.308 +				MailboxUid *mailbox = [message mailbox];
   5.309 +				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   5.310 +				if ([self inboxOnly] && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   5.311 +					continue;
   5.312 +
   5.313 +				MailAccount *account = [mailbox account];
   5.314 +				if (![self isAccountEnabled:account])
   5.315 +					continue;
   5.316 +
   5.317 +				if (![message isKindOfClass:Message_class])
   5.318 +					[self shutDownGrowlMailAndWarn:[NSString stringWithFormat:@"Message in notification was not a Message; it is %@", message]];
   5.319 +
   5.320 +				if (![message respondsToSelector:@selector(isRead)] || ![message isRead]) {
   5.321 +					/* Don't display read messages */
   5.322 +					[[self class] showNotificationForMessage:message];
   5.323 +				}
   5.324 +			}
   5.325 +			break;
   5.326 +		}
   5.327 +		case GrowlMailSummaryModeAlways: {
   5.328 +			if (!class_getClassMethod([MailAccount class], @selector(mailAccounts)))
   5.329 +				[self shutDownGrowlMailAndWarn:@"MailAccount does not respond to +mailAccounts"];
   5.330 +			if (!class_getInstanceMethod(Message_class, @selector(mailbox)))
   5.331 +				[self shutDownGrowlMailAndWarn:@"Message does not respond to -mailbox"];
   5.332 +
   5.333 +			NSArray *accounts = [MailAccount mailAccounts];
   5.334 +			unsigned accountsCount = [accounts count];
   5.335 +			NSCountedSet *accountSummary = [NSCountedSet setWithCapacity:accountsCount];
   5.336 +			NSCountedSet *accountJunkSummary = [NSCountedSet setWithCapacity:accountsCount];
   5.337 +			NSEnumerator *messagesEnum = [messages objectEnumerator];
   5.338 +			NSArray *junkMailboxUids = [MailAccount junkMailboxUids];
   5.339 +			Message *message;
   5.340 +			while ((message = [messagesEnum nextObject])) {
   5.341 +				MailboxUid *mailbox = [message mailbox];
   5.342 +				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   5.343 +				if ([self inboxOnly] && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   5.344 +					continue;
   5.345 +
   5.346 +				MailAccount *account = [mailbox account];
   5.347 +				if (![self isAccountEnabled:account])
   5.348 +					continue;
   5.349 +
   5.350 +				if (([message isJunk]) || [junkMailboxUids containsObject:[message mailbox]])
   5.351 +					[accountJunkSummary addObject:account];
   5.352 +				else
   5.353 +					[accountSummary addObject:account];
   5.354 +			}
   5.355 +			NSString *title = NSLocalizedStringFromTableInBundle(@"New mail", NULL, GMGetGrowlMailBundle(), "");
   5.356 +			NSString *titleJunk = NSLocalizedStringFromTableInBundle(@"New junk mail", NULL, GMGetGrowlMailBundle(), "");
   5.357 +			NSString *description;
   5.358 +
   5.359 +			MailAccount *account;
   5.360 +
   5.361 +			NSEnumerator *accountSummaryEnum = [accountSummary objectEnumerator];
   5.362 +			while ((account = [accountSummaryEnum nextObject])) {
   5.363 +				if (![self isAccountEnabled:account])
   5.364 +					continue;
   5.365 +
   5.366 +				unsigned summaryCount = [accountSummary countForObject:account];
   5.367 +				if (summaryCount) {
   5.368 +					if (summaryCount == 1) {
   5.369 +						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   5.370 +					} else {
   5.371 +						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   5.372 +					}
   5.373 +					[GrowlApplicationBridge notifyWithTitle:title
   5.374 +												description:description
   5.375 +										   notificationName:NEW_MAIL_NOTIFICATION
   5.376 +												   iconData:nil
   5.377 +												   priority:0
   5.378 +												   isSticky:NO
   5.379 +											   clickContext:@""];	// non-nil click context
   5.380 +				}
   5.381 +			}
   5.382 +
   5.383 +			NSEnumerator *accountJunkSummaryEnum = [accountJunkSummary objectEnumerator];
   5.384 +			while ((account = [accountJunkSummaryEnum nextObject])) {
   5.385 +				if (![self isAccountEnabled:account])
   5.386 +					continue;
   5.387 +
   5.388 +				unsigned summaryCount = [accountJunkSummary countForObject:account];
   5.389 +				if (summaryCount) {
   5.390 +					if (summaryCount == 1) {
   5.391 +						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   5.392 +					} else {
   5.393 +						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   5.394 +					}					
   5.395 +					[GrowlApplicationBridge notifyWithTitle:titleJunk
   5.396 +												description:description
   5.397 +										   notificationName:NEW_JUNK_MAIL_NOTIFICATION
   5.398 +												   iconData:nil
   5.399 +												   priority:0
   5.400 +												   isSticky:NO
   5.401 +											   clickContext:@""];	// non-nil click context
   5.402 +				}
   5.403 +			}
   5.404 +			break;
   5.405 +		}
   5.406 +	}
   5.407 +}
   5.408 +
   5.409 +- (void)showAllNotifications:(NSNotification *)notification
   5.410 +{
   5.411 +	if (([[notification name] rangeOfString:@"NSWindow"].location == NSNotFound) &&
   5.412 +		([[notification name] rangeOfString:@"NSMouse"].location == NSNotFound) &&
   5.413 +		([[notification name] rangeOfString:@"_NSThread"].location == NSNotFound)) {
   5.414 +		NSLog(@"%@", notification);
   5.415 +	}
   5.416 +}
   5.417 +
   5.418 +- (void)monitoredActivityStarted:(NSNotification *)notification
   5.419 +{
   5.420 +	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   5.421 +		messageCopies++;
   5.422 +#ifdef GROWL_MAIL_DEBUG
   5.423 +		NSLog(@"Copying a message: messageCopies is now %i", messageCopies);
   5.424 +#endif
   5.425 +		if (messageCopies <= 0)
   5.426 +			[self shutDownGrowlMailAndWarn:@"Number of message-copying operations overflowed. How on earth did you accomplish starting more than 2 billion copying operations at a time?!"];
   5.427 +	}
   5.428 +}
   5.429 +
   5.430 +- (void)monitoredActivityEnded:(NSNotification *)notification
   5.431 +{
   5.432 +	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   5.433 +		if (messageCopies <= 0)
   5.434 +			[self shutDownGrowlMailAndWarn:@"Number of message-copying operations went below 0. It is not possible to have a negative number of copying operations!"];
   5.435 +		messageCopies--;
   5.436 +#ifdef GROWL_MAIL_DEBUG
   5.437 +		NSLog(@"Finished copying a message: messageCopies is now %i", messageCopies);
   5.438 +#endif
   5.439 +	}
   5.440 +}
   5.441 +
   5.442 +#pragma mark Preferences
   5.443 +
   5.444 +- (BOOL) isAccountEnabled:(MailAccount *)account {
   5.445 +	BOOL isEnabled = YES;
   5.446 +	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   5.447 +	if (accountSettings) {
   5.448 +		NSNumber *value = [accountSettings objectForKey:[account path]];
   5.449 +		if (value)
   5.450 +			isEnabled = [value boolValue];
   5.451 +	}
   5.452 +	return isEnabled;
   5.453 +}
   5.454 +
   5.455 +- (void) setAccount:(MailAccount *)account enabled:(BOOL)yesOrNo {
   5.456 +	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   5.457 +	NSMutableDictionary *newSettings = [[accountSettings mutableCopy] autorelease];
   5.458 +	if (!newSettings)
   5.459 +		newSettings = [NSMutableDictionary dictionaryWithCapacity:1U];
   5.460 +	[newSettings setObject:[NSNumber numberWithBool:yesOrNo] forKey:[account path]];
   5.461 +	[[NSUserDefaults standardUserDefaults] setObject:newSettings forKey:@"GMAccounts"];
   5.462 +}
   5.463 +
   5.464 +#pragma mark Accessors
   5.465 +
   5.466 +- (BOOL) isEnabled {
   5.467 +	NSNumber *enabledNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMEnableGrowlMailBundle"];
   5.468 +	return enabledNum ? [enabledNum boolValue] : YES;
   5.469 +}
   5.470 +- (GrowlMailSummaryMode) summaryMode {
   5.471 +	NSNumber *summaryModeNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMSummaryMode"];
   5.472 +	return summaryModeNum ? [summaryModeNum intValue] : GrowlMailSummaryModeAutomatic;
   5.473 +}
   5.474 +- (BOOL) inboxOnly {
   5.475 +	NSNumber *inboxOnlyNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMInboxOnly"];
   5.476 +	return inboxOnlyNum ? [inboxOnlyNum boolValue] : YES;
   5.477 +}
   5.478 +
   5.479 +- (NSString *) titleFormat {
   5.480 +	NSString *titleFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMTitleFormat"];
   5.481 +	return titleFormat ? titleFormat : @"(%account) %sender";
   5.482 +}
   5.483 +- (NSString *) descriptionFormat {
   5.484 +	NSString *descriptionFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMDescriptionFormat"];
   5.485 +	return descriptionFormat ? descriptionFormat : @"%subject\n%body";
   5.486 +}
   5.487 +
   5.488 +@end
     6.1 --- a/Extras/GrowlMail/GrowlMailPreferencesModule.m	Fri Jun 05 21:49:20 2009 -0700
     6.2 +++ b/Extras/GrowlMail/GrowlMailPreferencesModule.m	Sat Jun 06 21:30:17 2009 -0700
     6.3 @@ -33,7 +33,7 @@
     6.4  //
     6.5  
     6.6  #import "GrowlMailPreferencesModule.h"
     6.7 -#import "GrowlMail.h"
     6.8 +#import "GrowlMailNotifier.h"
     6.9  
    6.10  @interface MailAccount(GrowlMail)
    6.11  + (NSArray *) remoteMailAccounts;
    6.12 @@ -103,7 +103,7 @@
    6.13  #pragma unused(aTableView)
    6.14  	MailAccount *account = [[MailAccount remoteMailAccounts] objectAtIndex:rowIndex];
    6.15  	if ([[aTableColumn identifier] isEqualToString:@"active"])
    6.16 -		return [NSNumber numberWithBool:[[GrowlMail sharedInstance] isAccountEnabled:account]];
    6.17 +		return [NSNumber numberWithBool:[[GrowlMailNotifier sharedNotifier] isAccountEnabled:account]];
    6.18  	else
    6.19  		return [account displayName];
    6.20  }
    6.21 @@ -111,7 +111,7 @@
    6.22  - (void) tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex {
    6.23  #pragma unused(aTableView,aTableColumn)
    6.24  	MailAccount *account = [[MailAccount remoteMailAccounts] objectAtIndex:rowIndex];
    6.25 -	[[GrowlMail sharedInstance] setAccount:account enabled:[anObject boolValue]];
    6.26 +	[[GrowlMailNotifier sharedNotifier] setAccount:account enabled:[anObject boolValue]];
    6.27  }
    6.28  
    6.29  @end
     7.1 --- a/Extras/GrowlMail/GrowlMail_Prefix.pch	Fri Jun 05 21:49:20 2009 -0700
     7.2 +++ b/Extras/GrowlMail/GrowlMail_Prefix.pch	Sat Jun 06 21:30:17 2009 -0700
     7.3 @@ -6,3 +6,5 @@
     7.4      #import <Foundation/Foundation.h>
     7.5      #import <AppKit/AppKit.h>
     7.6  #endif
     7.7 +
     7.8 +//#define GROWL_MAIL_DEBUG
     8.1 --- a/Extras/GrowlMail/Message+GrowlMail.m	Fri Jun 05 21:49:20 2009 -0700
     8.2 +++ b/Extras/GrowlMail/Message+GrowlMail.m	Sat Jun 06 21:30:17 2009 -0700
     8.3 @@ -33,7 +33,7 @@
     8.4  //
     8.5  
     8.6  #import "Message+GrowlMail.h"
     8.7 -#import "GrowlMail.h"
     8.8 +#import "GrowlMailNotifier.h"
     8.9  #import <AddressBook/AddressBook.h>
    8.10  #import <Growl/Growl.h>
    8.11  
    8.12 @@ -57,8 +57,9 @@
    8.13  
    8.14  	MessageBody *messageBody = nil;
    8.15  
    8.16 -	NSString *titleFormat = (NSString *)GMTitleFormatString();
    8.17 -	NSString *descriptionFormat = (NSString *)GMDescriptionFormatString();
    8.18 +	GrowlMailNotifier *notifier = [GrowlMailNotifier sharedNotifier];
    8.19 +	NSString *titleFormat = [notifier titleFormat];
    8.20 +	NSString *descriptionFormat = [notifier descriptionFormat];
    8.21  
    8.22  	if ([titleFormat rangeOfString:@"%body"].location != NSNotFound ||
    8.23  			[descriptionFormat rangeOfString:@"%body"].location != NSNotFound) {
    8.24 @@ -91,8 +92,9 @@
    8.25  	NSString *senderAddress = [sender uncommentedAddress];
    8.26  	NSString *subject = (NSString *)[self subject];
    8.27  	NSString *body;
    8.28 -	NSString *titleFormat = (NSString *)GMTitleFormatString();
    8.29 -	NSString *descriptionFormat = (NSString *)GMDescriptionFormatString();
    8.30 +	GrowlMailNotifier *notifier = [GrowlMailNotifier sharedNotifier];
    8.31 +	NSString *titleFormat = [notifier titleFormat];
    8.32 +	NSString *descriptionFormat = [notifier descriptionFormat];
    8.33  
    8.34  	/* The fullName selector is not available in Mail.app 2.0. */
    8.35  	if ([sender respondsToSelector:@selector(fullName)])
    8.36 @@ -196,7 +198,7 @@
    8.37  								   isSticky:NO
    8.38  							   clickContext:clickContext];	// non-nil click context
    8.39  
    8.40 -	[GrowlMail didFinishNotificationForMessage:self];
    8.41 +	[notifier didFinishNotificationForMessage:self];
    8.42  }
    8.43  
    8.44  @end