5 // Created by Andy Matuschak on 9/10/07.
6 // Copyright 2007 __MyCompanyName__. All rights reserved.
9 #import "SURegistrar.h"
10 #import "SURegistryProtocol.h"
12 #import "BLAuthentication.h"
14 NSString *SULaunchCountKey = @"SULaunchCount";
15 NSString *SUHasPromptedUserKey = @"SUHasPromptedUser";
16 NSString *SULaunchesRequiredForPromptKey = @"SULaunchesRequiredForPrompt";
18 @interface SURegistrar (Private)
20 - (int)launchesRequiredForPrompt;
21 - (BOOL)isNewSparkleUser;
23 - (void)attemptInstallation;
24 - (void)updateAppRegistration:(id <SURegistryProtocol>)registry;
25 - (NSString *)productIdentifier;
28 @implementation SURegistrar
30 + (SURegistrar *)sharedRegistrar
32 static SURegistrar *sharedRegistrar = nil;
33 if (sharedRegistrar == nil)
34 sharedRegistrar = [[SURegistrar alloc] init];
35 return sharedRegistrar;
38 // This method attempts to inform the Sparkle daemon about this app. If the Sparkle daemon isn't installed,
39 // it will ask the user if he wants to install it, but only if he's never been asked before and the app has been launched enough times.
40 - (void)attemptRegistration
42 // First, we try to connect to the Sparkle daemon.
43 id proxy = [[NSConnection rootProxyForConnectionWithRegisteredName:SUDaemonServiceName host:nil] retain];
46 [proxy setProtocolForProxy:@protocol(SURegistryProtocol)];
47 // We update the application's registration every time it launches in case something changes.
48 [self updateAppRegistration:proxy];
52 // Either the daemon isn't running right now, or the user is new to Sparkle.
53 if ([self isNewSparkleUser])
55 // They're a Sparkle virgin! But we don't want to disrupt the first run of this app, so have we launched enough times?
56 if ([self launchCount] >= [self launchesRequiredForPrompt])
57 [self attemptInstallation];
59 // Otherwise, the daemon isn't running, or they just don't want Sparkle. So we're done.
63 - (void)updateAppRegistration:(id <SURegistryProtocol>)registry
66 if ([delegate respondsToSelector:@selector(productForRegistrar:)]) // Does the delegate want to take control?
68 product = [delegate productForRegistrar:self];
72 // The app doesn't want any special behavior, so it's probably just a plain .app.
73 product = [[[SUProduct alloc] initWithMainBundle:[NSBundle mainBundle]] autorelease];
75 [registry registerProduct:product withIdentifier:[self productIdentifier]];
78 - (void)attemptInstallation
80 // First, though, we have to ask.
82 NSAlert *alert = [NSAlert alertWithMessageText:@"Would you like to keep your applications up-to-date?" defaultButton:@"Install Sparkle" alternateButton:@"Don't Install" otherButton:@"Learn More..." informativeTextWithFormat:@"Sparkle keeps APP_NAME and other supported applications up-to-date. Would you like to install Sparkle now?"];
83 [alert setIcon:[[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForImageResource:@"Sparkle"]]];
86 SUSetupSupportFolder();
91 // Right now, the behavior is that we're asking every user individually if he wants to install Sparkle so as to avoid authenticating for /Library.
92 // This is probably sub-optimal.
93 CFPreferencesSetValue((CFStringRef)SUHasPromptedUserKey, [NSNumber numberWithBool:YES], (CFStringRef)SUSparkleIdentifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
94 CFPreferencesSynchronize((CFStringRef)SUSparkleIdentifier, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
97 - (NSString *)productIdentifier
99 if ([delegate respondsToSelector:@selector(productIdentifierForRegistrar:)]) // Give the delegate a chance to provide a custom identifier.
100 return [delegate productIdentifierForRegistrar:self];
101 return [[NSBundle mainBundle] bundleIdentifier];
104 - (BOOL)isNewSparkleUser
106 BOOL hasPromptedUser;
108 hasPromptedUser = CFPreferencesGetAppBooleanValue((CFStringRef)SUHasPromptedUserKey, (CFStringRef)SUSparkleIdentifier, &validKey);
109 return !(hasPromptedUser && validKey);
112 - (void)setDelegate:aDelegate
114 delegate = aDelegate;
119 // Whenever the registrar is initialized, we increment the launch count. This assumes that the class is loaded on every launch, which may not be the case.
120 [[NSUserDefaults standardUserDefaults] setInteger:([[NSUserDefaults standardUserDefaults] integerForKey:SULaunchCountKey] + 1) forKey:SULaunchCountKey];
125 return [[NSUserDefaults standardUserDefaults] integerForKey:SULaunchCountKey];
128 - (int)launchesRequiredForPrompt
130 NSNumber *requirement = [[NSBundle mainBundle] objectForInfoDictionaryKey:SULaunchesRequiredForPromptKey];
131 if (!requirement) { return 2; } // Default is to prompt on the *second* launch.
132 return [requirement intValue];