Extras/GrowlMail/GrowlMailNotifier.m
author Peter Hosey <hg@boredzo.org>
Sat Jun 06 21:30:17 2009 -0700 (2009-06-06)
changeset 4210 c36ddc8e6b22
parent 4209 f8a902d33769
child 4277 61dc661bdb56
permissions -rw-r--r--
Make GrowlMail's main suicide-pill method into a public function. The core method lives on, and the function calls it through the shiny new singleton-instance static variable that I added in the previous commit.
     1 //
     2 //  GrowlMailNotifier.m
     3 //  GrowlMail
     4 //
     5 //  Created by Peter Hosey on 2009-05-10.
     6 //  Copyright 2009 Peter Hosey. All rights reserved.
     7 //
     8 
     9 #import "GrowlMailNotifier.h"
    10 #import "GrowlMail.h"
    11 #import "Message+GrowlMail.h"
    12 #import <objc/objc-runtime.h>
    13 
    14 #import "MessageFrameworkHeaders.h"
    15 
    16 #define AUTO_THRESHOLD	10
    17 
    18 #define	MAX_NOTIFICATION_THREADS	5
    19 
    20 static int activeNotificationThreads = 0;
    21 
    22 static int messageCopies = 0;
    23 
    24 static GrowlMailNotifier *sharedNotifier = nil;
    25 
    26 static BOOL notifierEnabled = YES;
    27 
    28 @implementation GrowlMailNotifier
    29 
    30 #pragma mark Panic buttons
    31 
    32 //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.
    33 - (void) shutDownGrowlMail {
    34 	[[NSNotificationCenter defaultCenter] removeObserver:self];
    35 	[GrowlApplicationBridge setGrowlDelegate:nil];
    36 }
    37 
    38 #pragma mark The circle of life
    39 
    40 + (id) sharedNotifier {
    41 	if (!sharedNotifier) {
    42 		//-init and -dealloc will each assign to sharedNotifier.
    43 		[[[GrowlMailNotifier alloc] init] autorelease];
    44 	}
    45 	return sharedNotifier;
    46 }
    47 
    48 - (id) init {
    49 	if (sharedNotifier) {
    50 		[self release];
    51 		return [sharedNotifier retain];
    52 	}
    53 
    54 	//No shared notifier yet; someone is trying to create one. If we previously disabled ourselves, abort this attempt.
    55 	if (!notifierEnabled) {
    56 		[self release];
    57 		return nil;
    58 	}
    59 
    60 	if((self = [super init])) {
    61 		NSNumber *automatic = [NSNumber numberWithInt:GrowlMailSummaryModeAutomatic];
    62 		NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    63 			@"(%account) %sender",         @"GMTitleFormat",
    64 			@"%subject\n%body",            @"GMDescriptionFormat",
    65 			automatic,                     @"GMSummaryMode",
    66 			[NSNumber numberWithBool:YES], @"GMEnableGrowlMailBundle",
    67 			[NSNumber numberWithBool:NO],  @"GMInboxOnly",
    68 			nil];
    69 		[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
    70 
    71 		[GrowlApplicationBridge setGrowlDelegate:self];
    72 
    73 		[[NSNotificationCenter defaultCenter] addObserver:self
    74 												 selector:@selector(messageStoreDidAddMessages:)
    75 													 name:@"MessageStoreMessagesAdded_inMainThread_"
    76 												   object:nil];
    77 		[[NSNotificationCenter defaultCenter] addObserver:self
    78 												 selector:@selector(monitoredActivityStarted:)
    79 													 name:@"MonitoredActivityStarted_inMainThread_"
    80 												   object:nil];
    81 		[[NSNotificationCenter defaultCenter] addObserver:self
    82 												 selector:@selector(monitoredActivityEnded:)
    83 													 name:@"MonitoredActivityEnded_inMainThread_"
    84 												   object:nil];
    85 		
    86 #ifdef GROWL_MAIL_DEBUG
    87 		/*
    88 		[[NSNotificationCenter defaultCenter] addObserver:self
    89 												 selector:@selector(showAllNotifications:)
    90 													 name:nil object:nil];
    91 		 */
    92 #endif
    93 		sharedNotifier = self;
    94 	}
    95 	return self;
    96 }
    97 
    98 - (void) dealloc {
    99 	[self shutDownGrowlMail];
   100 	[[NSNotificationCenter defaultCenter] removeObserver:self];
   101 	sharedNotifier = nil;
   102 
   103 	[super dealloc];
   104 }
   105 
   106 #pragma mark GrowlApplicationBridge delegate methods
   107 
   108 - (NSString *) applicationNameForGrowl {
   109 	return @"GrowlMail";
   110 }
   111 
   112 - (NSImage *) applicationIconForGrowl {
   113 	return [NSImage imageNamed:@"NSApplicationIcon"];
   114 }
   115 
   116 - (void) growlNotificationWasClicked:(NSString *)clickContext {
   117 	if ([clickContext length]) {
   118 		//Make sure we have all the methods we need.
   119 		if (!class_getClassMethod([Library class], @selector(messageWithMessageID:)))
   120 			GMShutDownGrowlMailAndWarn(@"Library does not respond to +messageWithMessageID:");
   121 		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(initForViewingMessage:showAllHeaders:viewingState:fromDefaults:)))
   122 			GMShutDownGrowlMailAndWarn(@"SingleMessageViewer does not respond to -initForViewingMessage:showAllHeaders:viewingState:fromDefaults:");
   123 		if (!class_getInstanceMethod([SingleMessageViewer class], @selector(showAndMakeKey:)))
   124 			GMShutDownGrowlMailAndWarn(@"SingleMessageViewer does not respond to -showAndMakeKey:");
   125 
   126 		Message *message = [Library messageWithMessageID:clickContext];
   127 		MessageViewingState *viewingState = [[MessageViewingState alloc] init];
   128 		SingleMessageViewer *messageViewer = [[SingleMessageViewer alloc] initForViewingMessage:message showAllHeaders:NO viewingState:viewingState fromDefaults:NO];
   129 		[viewingState release];
   130 		[messageViewer showAndMakeKey:YES];
   131 		[messageViewer release];
   132 		[Library markMessageAsViewed:message];
   133 	}
   134 	[NSApp activateIgnoringOtherApps:YES];
   135 }
   136 
   137 - (NSDictionary *) registrationDictionaryForGrowl {
   138 	// Register our ticket with Growl
   139 	NSArray *allowedNotifications = [NSArray arrayWithObjects:
   140 		NEW_MAIL_NOTIFICATION,
   141 		NEW_JUNK_MAIL_NOTIFICATION,
   142 		NEW_NOTE_NOTIFICATION,
   143 		nil];
   144 	NSDictionary *humanReadableNames = [NSDictionary dictionaryWithObjectsAndKeys:
   145 										NSLocalizedStringFromTableInBundle(@"New mail", nil, GMGetGrowlMailBundle(), ""), NEW_MAIL_NOTIFICATION,
   146 										NSLocalizedStringFromTableInBundle(@"New junk mail", nil, GMGetGrowlMailBundle(), ""), NEW_JUNK_MAIL_NOTIFICATION,
   147 										NSLocalizedStringFromTableInBundle(@"New note", nil, GMGetGrowlMailBundle(), ""), NEW_NOTE_NOTIFICATION,
   148 										nil];
   149 	NSArray *defaultNotifications = [NSArray arrayWithObject:NEW_MAIL_NOTIFICATION];
   150 
   151 	NSDictionary *ticket = [NSDictionary dictionaryWithObjectsAndKeys:
   152 		allowedNotifications, GROWL_NOTIFICATIONS_ALL,
   153 		defaultNotifications, GROWL_NOTIFICATIONS_DEFAULT,
   154 		humanReadableNames, GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES,
   155 		nil];
   156 #ifdef GROWL_MAIL_DEBUG
   157 	NSLog(@"%s: Returning Growl dictionary %@", __PRETTY_FUNCTION__, ticket);
   158 #endif
   159 
   160 	return ticket;
   161 }
   162 
   163 #pragma mark Mail notification handlers
   164 
   165 + (void)showNotificationForMessage:(Message *)message
   166 {
   167 	if (activeNotificationThreads < MAX_NOTIFICATION_THREADS) { 
   168 		activeNotificationThreads++;
   169 		
   170 		/* Why use a thread?
   171 		 *
   172 		 * If we want the message body, it may not be immediately available.
   173 		 * It can be retrieved without blocking if it's available, which we initially try.
   174 		 * However, if we really, really want it, we may have to request it in a blocking fashion:
   175 		 *		for example, if the user doesn't read the message and doesn't have Mail set to download it automatically,
   176 		 *		we'll never get it without blocking.
   177 		 *
   178 		 * Blocking the main thread is, of course, out of the question.
   179 		 *
   180 		 * We're making some assumptions about Mail's internals, but the fact that notifications are posted on auxiliary threads
   181 		 * and then again with a _inMainThread_ suffix on the main thread indicates that threads are being used for mail access elsewhere.
   182 		 */
   183 		[NSThread detachNewThreadSelector:@selector(GMShowNotificationPart1)
   184 								 toTarget:message
   185 							   withObject:nil];
   186 	} else {
   187 		[self performSelector:@selector(showNotificationForMessage:)
   188 				   withObject:message
   189 				   afterDelay:2.0];
   190 	}
   191 }
   192 
   193 - (void)didFinishNotificationForMessage:(Message *)message
   194 {
   195 #pragma unused(message)
   196 	activeNotificationThreads--;	
   197 }
   198 
   199 - (void)messageStoreDidAddMessages:(NSNotification *)notification {
   200 	if (![self isEnabled]) return;
   201 
   202 #ifdef GROWL_MAIL_DEBUG
   203 	NSLog(@"%s called", __PRETTY_FUNCTION__);
   204 #endif
   205 	
   206 	if (messageCopies) {
   207 #ifdef GROWL_MAIL_DEBUG
   208 		NSLog(@"Ignoring because %i message copies are in process", messageCopies);
   209 #endif
   210 		return;
   211 	}
   212 
   213 	Library *store = [notification object];
   214 	if (!store) {
   215 		GMShutDownGrowlMailAndWarn([NSString stringWithFormat:@"'%@' notification has no object", [notification name]]);
   216 	}
   217 	if ([store isKindOfClass:[LibraryStore class]]) {
   218 		//As of Tiger, this is normal; this notification is posted a couple times (perhaps once per inbox) with a LibraryStore object.
   219 		//This is not the notification we're looking for; we don't need to see its papers. We will move along now.
   220 		return;
   221 	}
   222 	//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.
   223 	//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.
   224 
   225 	NSDictionary *userInfo = [notification userInfo];
   226 	if (!userInfo) GMShutDownGrowlMailAndWarn(@"Notification had no userInfo");
   227 
   228 	NSArray *mailboxes = [userInfo objectForKey:@"mailboxes"];
   229 #ifdef GROWL_MAIL_DEBUG
   230 	NSLog(@"%s: Adding messages to mailboxes %@", __PRETTY_FUNCTION__, mailboxes);
   231 #endif
   232 
   233 	//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.
   234 	if (!(mailboxes && [mailboxes count])) return;
   235 
   236 	//Ignore a notification if we're ignoring all of the mailboxes involved.
   237 	Class MailAccount_class = [MailAccount class];
   238 	if (!class_getClassMethod(MailAccount_class, @selector(draftMailboxUids)))
   239 		GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +draftMailboxUids");
   240 	if (!class_getClassMethod(MailAccount_class, @selector(outboxMailboxUids)))
   241 		GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +outboxMailboxUids");
   242 	if (!class_getClassMethod(MailAccount_class, @selector(sentMessagesMailboxUids)))
   243 		GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +sentMessagesMailboxUids");
   244 	if (!class_getClassMethod(MailAccount_class, @selector(trashMailboxUids)))
   245 		GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +trashMailboxUids");
   246 	//We need this method to support the Inbox Only preference.
   247 	if (!class_getClassMethod(MailAccount_class, @selector(inboxMailboxUids)))
   248 		GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +inboxMailboxUids");
   249 
   250 	//Ignore messages being written.
   251 	NSMutableSet *mailboxesToIgnore = [NSMutableSet setWithArray:[MailAccount draftMailboxUids]];
   252 	//Ignore messages being sent.
   253 	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount outboxMailboxUids]]];
   254 	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount sentMessagesMailboxUids]]];
   255 	//Ignore messages being deleted.
   256 	[mailboxesToIgnore unionSet:[NSSet setWithArray:[MailAccount trashMailboxUids]]];
   257 
   258 	NSSet *mailboxesSet = [NSSet setWithArray:mailboxes];
   259 	NSMutableSet *mailboxesNotIgnored = [[mailboxesSet mutableCopy] autorelease];
   260 	[mailboxesNotIgnored minusSet:mailboxesToIgnore];
   261 	if ([mailboxesNotIgnored count] == 0U)
   262 		return;
   263 
   264 	NSArray *messages = [userInfo objectForKey:@"messages"];
   265 	if (!messages) GMShutDownGrowlMailAndWarn(@"Notification's userInfo has no messages");
   266 	
   267 #ifdef GROWL_MAIL_DEBUG
   268 	NSLog(@"%s: Mail added messages [1] to mailboxes [2].\n[1]: %@\n[2]: %@", __PRETTY_FUNCTION__, messages, mailboxes);
   269 #endif
   270 	
   271 	unsigned count = [messages count];
   272 
   273 	int summaryMode = [self summaryMode];
   274 	if (summaryMode == GrowlMailSummaryModeAutomatic) {
   275 		if (count >= AUTO_THRESHOLD)
   276 			summaryMode = GrowlMailSummaryModeAlways;
   277 		else
   278 			summaryMode = GrowlMailSummaryModeDisabled;
   279 	}
   280 
   281 #ifdef GROWL_MAIL_DEBUG
   282 	NSLog(@"Got %i new messages. Summary mode was %i and is now %i", count, [self summaryMode], summaryMode);
   283 #endif
   284 
   285 	Class Message_class = [Message class];
   286 
   287 	switch (summaryMode) {
   288 		default:
   289 		case GrowlMailSummaryModeDisabled: {
   290 			NSEnumerator *messagesEnum = [messages objectEnumerator];
   291 			Message *message;
   292 			while ((message = [messagesEnum nextObject])) {
   293 				MailboxUid *mailbox = [message mailbox];
   294 				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   295 				if ([self inboxOnly] && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   296 					continue;
   297 
   298 				MailAccount *account = [mailbox account];
   299 				if (![self isAccountEnabled:account])
   300 					continue;
   301 
   302 				if (![message isKindOfClass:Message_class])
   303 					GMShutDownGrowlMailAndWarn([NSString stringWithFormat:@"Message in notification was not a Message; it is %@", message]);
   304 
   305 				if (![message respondsToSelector:@selector(isRead)] || ![message isRead]) {
   306 					/* Don't display read messages */
   307 					[[self class] showNotificationForMessage:message];
   308 				}
   309 			}
   310 			break;
   311 		}
   312 		case GrowlMailSummaryModeAlways: {
   313 			if (!class_getClassMethod([MailAccount class], @selector(mailAccounts)))
   314 				GMShutDownGrowlMailAndWarn(@"MailAccount does not respond to +mailAccounts");
   315 			if (!class_getInstanceMethod(Message_class, @selector(mailbox)))
   316 				GMShutDownGrowlMailAndWarn(@"Message does not respond to -mailbox");
   317 
   318 			NSArray *accounts = [MailAccount mailAccounts];
   319 			unsigned accountsCount = [accounts count];
   320 			NSCountedSet *accountSummary = [NSCountedSet setWithCapacity:accountsCount];
   321 			NSCountedSet *accountJunkSummary = [NSCountedSet setWithCapacity:accountsCount];
   322 			NSEnumerator *messagesEnum = [messages objectEnumerator];
   323 			NSArray *junkMailboxUids = [MailAccount junkMailboxUids];
   324 			Message *message;
   325 			while ((message = [messagesEnum nextObject])) {
   326 				MailboxUid *mailbox = [message mailbox];
   327 				//If this mailbox is not an inbox, and we only care about inboxes, then skip this message.
   328 				if ([self inboxOnly] && ![[MailAccount inboxMailboxUids] containsObject:mailbox])
   329 					continue;
   330 
   331 				MailAccount *account = [mailbox account];
   332 				if (![self isAccountEnabled:account])
   333 					continue;
   334 
   335 				if (([message isJunk]) || [junkMailboxUids containsObject:[message mailbox]])
   336 					[accountJunkSummary addObject:account];
   337 				else
   338 					[accountSummary addObject:account];
   339 			}
   340 			NSString *title = NSLocalizedStringFromTableInBundle(@"New mail", NULL, GMGetGrowlMailBundle(), "");
   341 			NSString *titleJunk = NSLocalizedStringFromTableInBundle(@"New junk mail", NULL, GMGetGrowlMailBundle(), "");
   342 			NSString *description;
   343 
   344 			MailAccount *account;
   345 
   346 			NSEnumerator *accountSummaryEnum = [accountSummary objectEnumerator];
   347 			while ((account = [accountSummaryEnum nextObject])) {
   348 				if (![self isAccountEnabled:account])
   349 					continue;
   350 
   351 				unsigned summaryCount = [accountSummary countForObject:account];
   352 				if (summaryCount) {
   353 					if (summaryCount == 1) {
   354 						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   355 					} else {
   356 						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   357 					}
   358 					[GrowlApplicationBridge notifyWithTitle:title
   359 												description:description
   360 										   notificationName:NEW_MAIL_NOTIFICATION
   361 												   iconData:nil
   362 												   priority:0
   363 												   isSticky:NO
   364 											   clickContext:@""];	// non-nil click context
   365 				}
   366 			}
   367 
   368 			NSEnumerator *accountJunkSummaryEnum = [accountJunkSummary objectEnumerator];
   369 			while ((account = [accountJunkSummaryEnum nextObject])) {
   370 				if (![self isAccountEnabled:account])
   371 					continue;
   372 
   373 				unsigned summaryCount = [accountJunkSummary countForObject:account];
   374 				if (summaryCount) {
   375 					if (summaryCount == 1) {
   376 						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n 1 new mail", NULL, GMGetGrowlMailBundle(), "%@ is an account name"), [account displayName]];
   377 					} else {
   378 						description = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ \n %u new mails", NULL, GMGetGrowlMailBundle(), "%@ is an account name; %u becomes a number"), [account displayName], summaryCount];
   379 					}					
   380 					[GrowlApplicationBridge notifyWithTitle:titleJunk
   381 												description:description
   382 										   notificationName:NEW_JUNK_MAIL_NOTIFICATION
   383 												   iconData:nil
   384 												   priority:0
   385 												   isSticky:NO
   386 											   clickContext:@""];	// non-nil click context
   387 				}
   388 			}
   389 			break;
   390 		}
   391 	}
   392 }
   393 
   394 - (void)showAllNotifications:(NSNotification *)notification
   395 {
   396 	if (([[notification name] rangeOfString:@"NSWindow"].location == NSNotFound) &&
   397 		([[notification name] rangeOfString:@"NSMouse"].location == NSNotFound) &&
   398 		([[notification name] rangeOfString:@"_NSThread"].location == NSNotFound)) {
   399 		NSLog(@"%@", notification);
   400 	}
   401 }
   402 
   403 - (void)monitoredActivityStarted:(NSNotification *)notification
   404 {
   405 	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   406 		messageCopies++;
   407 #ifdef GROWL_MAIL_DEBUG
   408 		NSLog(@"Copying a message: messageCopies is now %i", messageCopies);
   409 #endif
   410 		if (messageCopies <= 0)
   411 			GMShutDownGrowlMailAndWarn(@"Number of message-copying operations overflowed. How on earth did you accomplish starting more than 2 billion copying operations at a time?!");
   412 	}
   413 }
   414 
   415 - (void)monitoredActivityEnded:(NSNotification *)notification
   416 {
   417 	if ([[[notification object] description] isEqualToString:@"Copying messages"]) {
   418 		if (messageCopies <= 0)
   419 			GMShutDownGrowlMailAndWarn(@"Number of message-copying operations went below 0. It is not possible to have a negative number of copying operations!");
   420 		messageCopies--;
   421 #ifdef GROWL_MAIL_DEBUG
   422 		NSLog(@"Finished copying a message: messageCopies is now %i", messageCopies);
   423 #endif
   424 	}
   425 }
   426 
   427 #pragma mark Preferences
   428 
   429 - (BOOL) isAccountEnabled:(MailAccount *)account {
   430 	BOOL isEnabled = YES;
   431 	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   432 	if (accountSettings) {
   433 		NSNumber *value = [accountSettings objectForKey:[account path]];
   434 		if (value)
   435 			isEnabled = [value boolValue];
   436 	}
   437 	return isEnabled;
   438 }
   439 
   440 - (void) setAccount:(MailAccount *)account enabled:(BOOL)yesOrNo {
   441 	NSDictionary *accountSettings = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMAccounts"];
   442 	NSMutableDictionary *newSettings = [[accountSettings mutableCopy] autorelease];
   443 	if (!newSettings)
   444 		newSettings = [NSMutableDictionary dictionaryWithCapacity:1U];
   445 	[newSettings setObject:[NSNumber numberWithBool:yesOrNo] forKey:[account path]];
   446 	[[NSUserDefaults standardUserDefaults] setObject:newSettings forKey:@"GMAccounts"];
   447 }
   448 
   449 #pragma mark Accessors
   450 
   451 - (BOOL) isEnabled {
   452 	NSNumber *enabledNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMEnableGrowlMailBundle"];
   453 	return enabledNum ? [enabledNum boolValue] : YES;
   454 }
   455 - (GrowlMailSummaryMode) summaryMode {
   456 	NSNumber *summaryModeNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMSummaryMode"];
   457 	return summaryModeNum ? [summaryModeNum intValue] : GrowlMailSummaryModeAutomatic;
   458 }
   459 - (BOOL) inboxOnly {
   460 	NSNumber *inboxOnlyNum = [[NSUserDefaults standardUserDefaults] objectForKey:@"GMInboxOnly"];
   461 	return inboxOnlyNum ? [inboxOnlyNum boolValue] : YES;
   462 }
   463 
   464 - (NSString *) titleFormat {
   465 	NSString *titleFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMTitleFormat"];
   466 	return titleFormat ? titleFormat : @"(%account) %sender";
   467 }
   468 - (NSString *) descriptionFormat {
   469 	NSString *descriptionFormat = [[NSUserDefaults standardUserDefaults] stringForKey:@"GMDescriptionFormat"];
   470 	return descriptionFormat ? descriptionFormat : @"%subject\n%body";
   471 }
   472 
   473 #pragma mark Panic buttons
   474 
   475 //This is a suicide pill. GrowlMail calls this function any time it detects a change in Mail's implementation, such as a missing method or an object of the wrong class.
   476 void GMShutDownGrowlMailAndWarn(NSString *specificWarning) {
   477 	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__);
   478 	if (specificWarning)
   479 		NSLog(@"Furthermore, the caller provided a more specific message: %@", specificWarning);
   480 
   481 	[sharedNotifier shutDownGrowlMail];
   482 
   483 	//Prevent ourselves from re-enabling later.
   484 	notifierEnabled = NO;
   485 }
   486 
   487 @end