Fix an apparent leak: We put the download-started date into our dictionary under the download's identifier, but then tried to remove the date from the dictionary under the download itself. Thus, we leaked both the date and the copy of the identifier.
2 Copyright (c) The Growl Project, 2004-2005
6 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9 1. Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 2. Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 3. Neither the name of Growl nor the names of its contributors
15 may be used to endorse or promote products derived from this software
16 without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 // Created by Kevin Ballard on 10/29/04.
28 // Copyright 2004 Kevin Ballard. All rights reserved.
31 #import "GrowlSafari.h"
32 #import "GSWebBookmark.h"
33 #import <Growl/Growl.h>
34 #import <objc/objc-runtime.h>
37 #define SAFARI_VERSION_2_0 412
38 #define SAFARI_VERSION_3_0 523
40 // How long should we wait (in seconds) before it's a long download?
41 static double longDownload = 15.0;
42 static int safariVersion;
43 static NSMutableDictionary *dates = nil;
45 // Using method swizzling as outlined here:
46 // http://www.cocoadev.com/index.pl?MethodSwizzling
47 // A couple of modifications made to support swizzling class methods
49 static BOOL PerformSwizzle(Class aClass, SEL orig_sel, SEL alt_sel, BOOL forInstance) {
50 // First, make sure the class isn't nil
52 Method orig_method = nil, alt_method = nil;
54 // Next, look for the methods
56 orig_method = class_getInstanceMethod(aClass, orig_sel);
57 alt_method = class_getInstanceMethod(aClass, alt_sel);
59 orig_method = class_getClassMethod(aClass, orig_sel);
60 alt_method = class_getClassMethod(aClass, alt_sel);
63 // If both are found, swizzle them
64 if (orig_method && alt_method) {
67 temp = orig_method->method_imp;
68 orig_method->method_imp = alt_method->method_imp;
69 alt_method->method_imp = temp;
73 // This bit stolen from SubEthaFari's source
74 NSLog(@"GrowlSafari Error: Original (selector %s) %@, Alternate (selector %s) %@",
76 orig_method ? @"was found" : @"not found",
78 alt_method ? @"was found" : @"not found");
81 NSLog(@"%@", @"GrowlSafari Error: No class to swizzle methods in");
87 static void setDownloadStarted(id dl) {
89 dates = [[NSMutableDictionary alloc] init];
91 [dates setObject:[NSDate date] forKey:[dl identifier]];
94 static NSDate *dateStarted(id dl) {
96 return [dates objectForKey:[dl identifier]];
101 static BOOL isLongDownload(id dl) {
102 NSDate *date = dateStarted(dl);
103 return (date && -[date timeIntervalSinceNow] > longDownload);
106 static void setDownloadFinished(id dl) {
107 [dates removeObjectForKey:[dl identifier]];
110 @implementation GrowlSafari
111 + (NSBundle *) bundle {
112 return [NSBundle bundleWithIdentifier:@"com.growl.GrowlSafari"];
115 + (NSString *) bundleVersion {
116 return [[[GrowlSafari bundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey];
119 + (void) initialize {
120 NSString *growlPath = [[[GrowlSafari bundle] privateFrameworksPath] stringByAppendingPathComponent:@"Growl.framework"];
121 NSBundle *growlBundle = [NSBundle bundleWithPath:growlPath];
123 if (growlBundle && [growlBundle load]) {
124 // Register ourselves as a Growl delegate
125 [GrowlApplicationBridge setGrowlDelegate:self];
127 safariVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey] intValue];
128 // NSLog(@"%d",safariVersion);
130 // NSLog(@"Patching DownloadProgressEntry...");
131 Class class = NSClassFromString(@"DownloadProgressEntry");
132 PerformSwizzle(class, @selector(setDownloadStage:), @selector(mySetDownloadStage:), YES);
134 if (safariVersion<SAFARI_VERSION_3_0)
135 PerformSwizzle(class, @selector(updateDiskImageStatus:), @selector(myUpdateDiskImageStatus:), YES);
137 PerformSwizzle(class, @selector(_updateDiskImageStatus:), @selector(myUpdateDiskImageStatus:), YES);
139 PerformSwizzle(class, @selector(initWithDownload:mayOpenWhenDone:allowOverwrite:),
140 @selector(myInitWithDownload:mayOpenWhenDone:allowOverwrite:),
143 Class webBookmarkClass = NSClassFromString(@"WebBookmark");
144 if (webBookmarkClass)
145 [[GSWebBookmark class] poseAsClass:webBookmarkClass];
147 NSLog(@"Loaded GrowlSafari %@", [GrowlSafari bundleVersion]);
148 NSDictionary *infoDictionary = [GrowlApplicationBridge frameworkInfoDictionary];
149 NSLog(@"Using Growl.framework %@ (%@)",
150 [infoDictionary objectForKey:@"CFBundleShortVersionString"],
151 [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey]);
153 NSLog(@"Could not load Growl.framework, GrowlSafari disabled");
158 #pragma mark GrowlApplicationBridge delegate methods
160 + (NSString *) applicationNameForGrowl {
161 return @"GrowlSafari";
164 + (NSImage *) applicationIconForGrowl {
165 return [NSImage imageNamed:@"NSApplicationIcon"];
168 + (NSDictionary *) registrationDictionaryForGrowl {
169 NSBundle *bundle = [GrowlSafari bundle];
170 NSArray *array = [[NSArray alloc] initWithObjects:
171 NSLocalizedStringFromTableInBundle(@"Short Download Complete", nil, bundle, @""),
172 NSLocalizedStringFromTableInBundle(@"Download Complete", nil, bundle, @""),
173 NSLocalizedStringFromTableInBundle(@"Disk Image Status", nil, bundle, @""),
174 NSLocalizedStringFromTableInBundle(@"Compression Status", nil, bundle, @""),
175 NSLocalizedStringFromTableInBundle(@"New feed entry", nil, bundle, @""),
177 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
178 array, GROWL_NOTIFICATIONS_DEFAULT,
179 array, GROWL_NOTIFICATIONS_ALL,
186 + (void) growlNotificationWasClicked:(id)clickContext {
187 NSURL *url = [[NSURL alloc] initWithString:clickContext];
188 [[NSWorkspace sharedWorkspace] openURL:url];
192 + (void) notifyRSSUpdate:(WebBookmark *)bookmark newEntries:(int)newEntries {
193 NSBundle *bundle = [GrowlSafari bundle];
194 NSMutableString *description = [[NSMutableString alloc]
195 initWithFormat:newEntries == 1 ? NSLocalizedStringFromTableInBundle(@"%d new entry", nil, bundle, @"") : NSLocalizedStringFromTableInBundle(@"%d new entries", nil, bundle, @""),
197 [bookmark unreadRSSCount]];
198 if (newEntries != [bookmark unreadRSSCount])
199 [description appendFormat:NSLocalizedStringFromTableInBundle(@" (%d unread)", nil, bundle, @""), [bookmark unreadRSSCount]];
201 NSString *title = [bookmark title];
202 [GrowlApplicationBridge notifyWithTitle:(title ? title : [bookmark URLString])
203 description:description
204 notificationName:NSLocalizedStringFromTableInBundle(@"New feed entry", nil, bundle, @"")
208 clickContext:[bookmark URLString]];
209 [description release];
213 @implementation NSObject (GrowlSafariPatch)
214 - (void) mySetDownloadStage:(int)stage {
215 int oldStage = [self downloadStage];
217 //NSLog(@"mySetDownloadStage:%d -> %d", oldStage, stage);
218 [self mySetDownloadStage:stage];
219 if (dateStarted(self)) {
220 if ( (safariVersion < SAFARI_VERSION_3_0 && stage == 2) || (safariVersion >= SAFARI_VERSION_3_0 && stage == 1) ) {
221 NSBundle *bundle = [GrowlSafari bundle];
222 NSString *description = [[NSString alloc] initWithFormat:
223 NSLocalizedStringFromTableInBundle(@"%@", nil, bundle, @""),
224 [[self gsDownloadPath] lastPathComponent]];
225 [GrowlApplicationBridge notifyWithTitle:NSLocalizedStringFromTableInBundle(@"Decompressing File", nil, bundle, @"")
226 description:description
227 notificationName:NSLocalizedStringFromTableInBundle(@"Compression Status", nil, bundle, @"")
232 [description release];
233 } else if ( (safariVersion < SAFARI_VERSION_3_0 && stage == 9 && oldStage != 9) ||
234 (safariVersion >= SAFARI_VERSION_3_0 && stage == 8 && oldStage != 8) ) {
235 NSBundle *bundle = [GrowlSafari bundle];
236 NSString *description = [[NSString alloc] initWithFormat:
237 NSLocalizedStringFromTableInBundle(@"%@", nil, bundle, @""),
238 [[self gsDownloadPath] lastPathComponent]];
239 [GrowlApplicationBridge notifyWithTitle:NSLocalizedStringFromTableInBundle(@"Copying Disk Image", nil, bundle, @"")
240 description:description
241 notificationName:NSLocalizedStringFromTableInBundle(@"Disk Image Status", nil, bundle, @"")
246 [description release];
247 } else if ( (safariVersion < SAFARI_VERSION_2_0 && stage == 13) ||
248 ((safariVersion >= SAFARI_VERSION_2_0 && safariVersion < SAFARI_VERSION_3_0) && stage == 15) ||
249 (safariVersion >= SAFARI_VERSION_3_0 && stage == 13) ) {
250 NSBundle *bundle = [GrowlSafari bundle];
251 NSString *notificationName = isLongDownload(self) ? NSLocalizedStringFromTableInBundle(@"Download Complete", nil, bundle, @"") : NSLocalizedStringFromTableInBundle(@"Short Download Complete", nil, bundle, @"");
252 setDownloadFinished(self);
253 NSString *description = [[NSString alloc] initWithFormat:
254 NSLocalizedStringFromTableInBundle(@"%@", nil, bundle, "Message shown when a download is complete, where %@ becomes the filename"),
256 [GrowlApplicationBridge notifyWithTitle:NSLocalizedStringFromTableInBundle(@"Download Complete", nil, bundle, @"")
257 description:description
258 notificationName:notificationName
263 [description release];
265 } else if (stage == 0) {
266 setDownloadStarted(self);
270 - (void) myUpdateDiskImageStatus:(NSDictionary *)status {
271 int oldStage = [self downloadStage];
272 [self myUpdateDiskImageStatus:status];
273 //NSLog(@"myUpdateDiskImageStatus:%@ stage=%d -> %d", status, oldStage, [self downloadStage]);
275 if (dateStarted(self)
276 && ( (safariVersion < SAFARI_VERSION_3_0 && oldStage == 3) || (safariVersion >= SAFARI_VERSION_3_0 && oldStage == 7) )
277 && [self downloadStage] == 8
278 && [[status objectForKey:@"status-stage"] isEqualToString:@"attach"]) {
279 NSBundle *bundle = [GrowlSafari bundle];
280 NSString *description = [[NSString alloc] initWithFormat:
281 NSLocalizedStringFromTableInBundle(@"%@", nil, bundle, @""),
282 [[self gsDownloadPath] lastPathComponent]];
283 [GrowlApplicationBridge notifyWithTitle:NSLocalizedStringFromTableInBundle(@"Mounting Disk Image", nil, bundle, @"")
284 description:description
285 notificationName:NSLocalizedStringFromTableInBundle(@"Disk Image Status", nil, bundle, @"")
290 [description release];
294 // This is to make sure we're done with the pre-saved downloads
295 - (id) myInitWithDownload:(id)fp8 mayOpenWhenDone:(BOOL)fp12 allowOverwrite:(BOOL)fp16 {
296 id retval = [self myInitWithDownload:fp8 mayOpenWhenDone:fp12 allowOverwrite:fp16];
297 setDownloadStarted(self);
301 - (NSString*) gsDownloadPath {
302 if (safariVersion<SAFARI_VERSION_3_0)
303 return [self downloadPath];
305 return [self currentPath];