forked from sparkle-project/Sparkle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SUUpdater.m
236 lines (202 loc) · 8.97 KB
/
SUUpdater.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
//
// SUUpdater.m
// Sparkle
//
// Created by Andy Matuschak on 1/4/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#import "Sparkle.h"
#import "SUUpdater.h"
@interface SUUpdater (Private)
- (void)beginUpdateCycle;
- (NSArray *)feedParameters;
- (BOOL)automaticallyUpdates;
- (BOOL)shouldScheduleUpdateCheck;
- (NSTimeInterval)checkInterval;
@end
@implementation SUUpdater
#pragma mark Initialization
static SUUpdater *sharedUpdater = nil;
// SUUpdater's a singleton now! And I'm enforcing it!
// This will probably break the world if you try to write a Sparkle-enabled plugin for a Sparkle-enabled app.
+ (SUUpdater *)sharedUpdater
{
if (sharedUpdater == nil)
sharedUpdater = [[[self class] alloc] init];
return sharedUpdater;
}
- (id)init
{
self = [super init];
if (sharedUpdater)
{
[self release];
self = sharedUpdater;
}
else if (self != nil)
{
sharedUpdater = self;
[self setHostBundle:[NSBundle mainBundle]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:NSApp];
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)note
{
// If the user has been asked about automatic checks and said no, get out of here.
if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] &&
[[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKey] == NO) { return; }
// Does the delegate want to take care of the logic for when we should ask permission to update?
if ([delegate respondsToSelector:@selector(shouldPromptForPermissionToCheckForUpdates)])
{
if ([delegate shouldPromptForPermissionToCheckForUpdates])
[SUUpdatePermissionPrompt promptWithHostBundle:hostBundle delegate:self];
}
// Has he been asked already? And don't ask if the host has a default value set in its Info.plist.
else if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] == nil &&
[hostBundle objectForInfoDictionaryKey:SUEnableAutomaticChecksKey] == nil)
{
if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKeyOld])
[[SUUserDefaults standardUserDefaults] setBool:[[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKeyOld] forKey:SUEnableAutomaticChecksKey];
// Now, we don't want to ask the user for permission to do a weird thing on the first launch.
// We wait until the second launch.
else if ([[SUUserDefaults standardUserDefaults] boolForKey:SUHasLaunchedBeforeKey] == NO)
[[SUUserDefaults standardUserDefaults] setBool:YES forKey:SUHasLaunchedBeforeKey];
else
[SUUpdatePermissionPrompt promptWithHostBundle:hostBundle delegate:self];
}
// We check if the user's said they want updates, or they haven't said anything, and the default is set to checking.
if ([self shouldScheduleUpdateCheck])
[self beginUpdateCycle];
}
- (void)updatePermissionPromptFinishedWithResult:(SUPermissionPromptResult)result
{
BOOL automaticallyCheck = (result == SUAutomaticallyCheck);
[[SUUserDefaults standardUserDefaults] setBool:automaticallyCheck forKey:SUEnableAutomaticChecksKey];
if ([self automaticallyUpdates])
[self beginUpdateCycle];
}
- (void)beginUpdateCycle
{
// How long has it been since last we checked for an update?
NSDate *lastCheckDate = [[SUUserDefaults standardUserDefaults] objectForKey:SULastCheckTimeKey];
if (!lastCheckDate) { lastCheckDate = [NSDate distantPast]; }
NSTimeInterval intervalSinceCheck = [[NSDate date] timeIntervalSinceDate:lastCheckDate];
// Now we want to figure out how long until we check again.
NSTimeInterval delayUntilCheck;
if (intervalSinceCheck < [self checkInterval])
delayUntilCheck = ([self checkInterval] - intervalSinceCheck); // It hasn't been long enough.
else
delayUntilCheck = 0; // We're overdue! Run one now.
checkTimer = [NSTimer scheduledTimerWithTimeInterval:delayUntilCheck target:self selector:@selector(checkForUpdatesInBackground) userInfo:nil repeats:NO];
}
- (void)checkForUpdatesInBackground
{
[self checkForUpdatesWithDriver:[[[([self automaticallyUpdates] ? [SUAutomaticUpdateDriver class] : [SUScheduledUpdateDriver class]) alloc] init] autorelease]];
}
- (IBAction)checkForUpdates:sender
{
[self checkForUpdatesWithDriver:[[[SUUserInitiatedUpdateDriver alloc] init] autorelease]];
}
- (void)checkForUpdatesWithDriver:(SUUpdateDriver *)d
{
if ([self updateInProgress]) { return; }
if (checkTimer) { [checkTimer invalidate]; checkTimer = nil; }
// A value in the user defaults overrides one in the Info.plist (so preferences panels can be created wherein users choose between beta / release feeds).
NSString *appcastString = [[SUUserDefaults standardUserDefaults] objectForKey:SUFeedURLKey];
if (!appcastString)
appcastString = [hostBundle objectForInfoDictionaryKey:SUFeedURLKey];
if (!appcastString)
[NSException raise:@"SUNoFeedURL" format:@"You must specify the URL of the appcast as the SUFeedURLKey in either the Info.plist or the user defaults!"];
NSCharacterSet* quoteSet = [NSCharacterSet characterSetWithCharactersInString: @"\"\'"]; // Some feed publishers add quotes; strip 'em.
NSURL *feedURL = [[NSURL URLWithString:[appcastString stringByTrimmingCharactersInSet:quoteSet]] URLWithParameters:[self feedParameters]];
driver = [d retain];
if ([driver delegate] == nil) { [driver setDelegate:delegate]; }
[driver addObserver:self forKeyPath:@"finished" options:0 context:NULL];
[driver checkForUpdatesAtURL:feedURL hostBundle:hostBundle];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object != driver) { return; }
[driver removeObserver:self forKeyPath:@"finished"];
[driver release]; driver = nil;
if ([self shouldScheduleUpdateCheck])
checkTimer = [NSTimer scheduledTimerWithTimeInterval:[self checkInterval] target:self selector:@selector(checkForUpdatesInBackground) userInfo:nil repeats:NO];
}
- (BOOL)shouldScheduleUpdateCheck
{
// Breaking this down for readability:
// If the user says he wants automatic update checks, let's do it.
if ([[SUUserDefaults standardUserDefaults] boolForKey:SUEnableAutomaticChecksKey] == YES)
return YES;
// If the user hasn't said anything, but the developer says we should do it, let's do it.
if ([[SUUserDefaults standardUserDefaults] objectForKey:SUEnableAutomaticChecksKey] == nil &&
[[hostBundle objectForInfoDictionaryKey:SUEnableAutomaticChecksKey] boolValue] == YES)
return YES;
return NO; // Otherwise, don't bothe.r
}
- (BOOL)automaticallyUpdates
{
// If the SUAllowsAutomaticUpdatesKey exists and is set to NO, return NO.
if ([hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] &&
[[hostBundle objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] boolValue] == NO)
return NO;
// If we're not using DSA signatures, we aren't going to trust any updates automatically.
if ([[hostBundle objectForInfoDictionaryKey:SUExpectsDSASignatureKey] boolValue] != YES)
return NO;
// If there's no setting, or it's set to no, we're not automatically updating.
if ([[SUUserDefaults standardUserDefaults] boolForKey:SUAutomaticallyUpdateKey] != YES)
return NO;
return YES; // Otherwise, we're good to go.
}
- (NSArray *)feedParameters
{
BOOL sendingSystemProfile = ([[SUUserDefaults standardUserDefaults] boolForKey:SUSendProfileInfoKey] == YES);
NSArray *parameters = [NSArray array];
if ([delegate respondsToSelector:@selector(feedParametersForUpdater:sendingSystemProfile:)])
parameters = [parameters arrayByAddingObjectsFromArray:[delegate feedParametersForUpdater:self sendingSystemProfile:sendingSystemProfile]];
if (sendingSystemProfile)
parameters = [parameters arrayByAddingObjectsFromArray:[hostBundle systemProfile]];
return parameters;
}
- (NSTimeInterval)checkInterval
{
NSTimeInterval checkInterval = 0;
// Find the stored check interval. User defaults override Info.plist.
if ([[SUUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey])
checkInterval = [[[SUUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey] doubleValue];
else if ([hostBundle objectForInfoDictionaryKey:SUScheduledCheckIntervalKey])
checkInterval = [[hostBundle objectForInfoDictionaryKey:SUScheduledCheckIntervalKey] doubleValue];
if (checkInterval < SU_MIN_CHECK_INTERVAL) // This can also mean one that isn't set.
checkInterval = SU_DEFAULT_CHECK_INTERVAL;
return checkInterval;
}
- (void)dealloc
{
[hostBundle release];
[delegate release];
if (checkTimer) { [checkTimer invalidate]; }
[super dealloc];
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
if ([item action] == @selector(checkForUpdates:))
return ![self updateInProgress];
return YES;
}
- (void)setDelegate:aDelegate
{
[delegate release];
delegate = [aDelegate retain];
}
- (void)setHostBundle:(NSBundle *)hb
{
[hostBundle release];
hostBundle = [hb retain];
[[SUUserDefaults standardUserDefaults] setIdentifier:[hostBundle bundleIdentifier]];
}
- (BOOL)updateInProgress
{
return driver && ([driver finished] == NO);
}
@end