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