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