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