2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "AIMenuController.h"
18 #import "AIPreferenceController.h"
19 #import "CPFVersionChecker.h"
20 #import "ESVersionCheckerWindowController.h"
21 #import <AIUtilities/AIDictionaryAdditions.h>
22 #import <AIUtilities/AIMenuAdditions.h>
23 #import <AIUtilities/AINetworkConnectivity.h>
25 #define VERSION_CHECKER_TITLE AILocalizedString(@"Check for Updates...",nil)
26 #define VERSION_PLIST_URL @"http://www.adiumx.com/version.plist"
27 #define VERSION_PLIST_KEY @"adium-version"
28 #define VERSION_BETA_PLIST_KEY @"adium-beta-version"
30 #define VERSION_CHECK_INTERVAL 24 //24 hours
31 #define BETA_VERSION_CHECK_INTERVAL 4 //4 hours - Beta releases have a nice annoying refresh >:D
33 #define VERSION_CHECKER_DEFAULTS @"VersionChecker Defaults"
35 @interface CPFVersionChecker (PRIVATE)
36 - (void)_requestVersionDict;
37 - (void)_versionReceived:(NSDictionary *)versionDict;
38 - (NSDate *)dateOfThisBuild;
42 * @class CPFVersionChecker
43 * @brief Checks for new releases of Adium
45 * The version checker checks for new releases of Adium and notifies the user when one is available.
47 * When the beta flag is set, we will version check from the beta key. This allows beta releases to receive update
48 * notifications separately from regular releases. Version updates will also be more frequent and cannot be disabled.
49 * This is to discourage the use of beta releases after a regular release is made without completely preventing the
50 * program from launching.
52 @implementation CPFVersionChecker
55 * @brief Install the version checker
59 //Configure our default preferences
60 [[adium preferenceController] registerDefaults:[NSDictionary dictionaryNamed:VERSION_CHECKER_DEFAULTS
61 forClass:[self class]]
62 forGroup:PREF_GROUP_UPDATING];
64 //Menu item for checking manually
65 versionCheckerMenuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:VERSION_CHECKER_TITLE
67 action:@selector(manualCheckForNewVersion:)
68 keyEquivalent:@""] autorelease];
69 [[adium menuController] addMenuItem:versionCheckerMenuItem toLocation:LOC_Adium_About];
71 //Observe connectivity changes
72 [[NSNotificationCenter defaultCenter] addObserver:self
73 selector:@selector(networkConnectivityChanged:)
74 name:AINetwork_ConnectivityChanged
77 //Check for an update now
78 [self automaticCheckForNewVersion:nil];
80 //Check for updates again every X hours (60 seconds * 60 minutes * X hours)
81 timer = [[NSTimer scheduledTimerWithTimeInterval:(60 * 60 * (BETA_RELEASE ? BETA_VERSION_CHECK_INTERVAL : VERSION_CHECK_INTERVAL))
83 selector:@selector(automaticCheckForNewVersion:)
89 * @brief Uninstall the version checker
91 - (void)uninstallPlugin
94 [timer release]; timer = nil;
98 //New version checking -------------------------------------------------------------------------------------------------
99 #pragma mark New version checking
101 * @brief Check for a new release of Adium (Notify on failure)
103 * Checks for a new release of Adium. The user will be notified when checking has completed, either with a new
104 * release or a message that their current release is up to date.
106 - (void)manualCheckForNewVersion:(id)sender
108 checkingManually = YES;
109 checkWhenConnectionBecomesAvailable = NO;
110 [self _requestVersionDict];
114 * @brief Check for a new release of Adium (Silent on failure)
116 * Check for a new release of Adium without notifying the user on a false result.
117 * Call this method when the user has not explicitely requested the version check.
119 - (void)automaticCheckForNewVersion:(id)sender
121 BOOL updateAutomatically = [[[adium preferenceController] preferenceForKey:KEY_CHECK_AUTOMATICALLY
122 group:PREF_GROUP_UPDATING] boolValue];
124 if(BETA_RELEASE || updateAutomatically){
125 if([AINetworkConnectivity networkIsReachable]){
126 //If the network is available, check for updates now
127 checkingManually = NO;
128 checkWhenConnectionBecomesAvailable = NO;
129 [self _requestVersionDict];
131 //If the network is not available, check when it becomes available
132 checkWhenConnectionBecomesAvailable = YES;
138 * @brief When a network connection becomes available, check for an update if we haven't already
140 - (void)networkConnectivityChanged:(NSNotification *)notification
142 if(checkWhenConnectionBecomesAvailable && [[notification object] intValue]){
143 [self automaticCheckForNewVersion:nil];
148 * @brief Invoked when version information is received
150 * Parse the version dictionary and notify the user (if necessary) of a new release or that their current
151 * version is the newest.
152 * @param versionDict Dictionary from the web containing version numbers of the most recent releases
154 - (void)_versionReceived:(NSDictionary *)versionDict
156 NSString *number = [versionDict objectForKey:VERSION_PLIST_KEY];
157 NSDate *newestDate = nil;
159 //Get the newest version date from the passed version dict
160 if(versionDict && number){
161 newestDate = [NSDate dateWithTimeIntervalSince1970:[number doubleValue]];
164 //Load relevant dates which we weren't passed
165 NSDate *thisDate = [self dateOfThisBuild];
166 NSDate *lastDateDisplayedToUser = [[adium preferenceController] preferenceForKey:KEY_LAST_UPDATE_ASKED
167 group:PREF_GROUP_UPDATING];
169 //If the user has already been informed of this update previously, don't bother them
170 if(checkingManually || !lastDateDisplayedToUser || (![lastDateDisplayedToUser isEqualToDate:newestDate])){
172 //Display connection error message
173 if(checkingManually) [ESVersionCheckerWindowController showCannotConnectWindow];
174 }else if([thisDate isEqualToDate:newestDate] || [thisDate isEqualToDate:[thisDate laterDate:newestDate]]){
175 //Display the 'up to date' message if the user checked for updates manually
176 if(checkingManually) [ESVersionCheckerWindowController showUpToDateWindow];
178 //Display 'update' message always
179 [ESVersionCheckerWindowController showUpdateWindowFromBuild:thisDate toBuild:newestDate];
181 //Remember that the user has been prompted for this version so we don't bug them about it again
182 [[adium preferenceController] setPreference:newestDate forKey:KEY_LAST_UPDATE_ASKED
183 group:PREF_GROUP_UPDATING];
187 //Beta Expiration (Designed to be annoying)
188 //Beta expiration checking is performed in addition to regular version checking
190 NSString *betaNumber = [versionDict objectForKey:VERSION_BETA_PLIST_KEY];
191 NSDate *betaDate = nil;
193 if(versionDict && number) betaDate = [NSDate dateWithTimeIntervalSince1970:[betaNumber doubleValue]];
195 [ESVersionCheckerWindowController showCannotConnectWindow];
196 }else if(![thisDate isEqualToDate:betaDate] && ![thisDate isEqualToDate:[thisDate laterDate:betaDate]]){
197 [ESVersionCheckerWindowController showUpdateWindowFromBuild:thisDate toBuild:betaDate];
203 //Build Dates ----------------------------------------------------------------------------------------------------------
204 #pragma mark Build Dates
206 * @brief Returns the date of this build
208 - (NSDate *)dateOfThisBuild
212 NSString *path = [[NSBundle mainBundle] pathForResource:@"buildnum" ofType:nil];
213 NSMutableData *data = [NSMutableData dataWithContentsOfFile:path];
215 [data increaseLengthBy:1]; //nul-terminates.
217 const char *ptr = [data bytes];
219 unsigned len = [data length];
222 //first character: 'r'. skip it.
224 if(i >= len) goto end;
226 //grab the build number.
227 unsigned long buildnum = strtoul(ptr+i, &nextptr, 10);
228 # pragma unused(buildnum)
231 //skip the '|' (with a space on each side of it).
233 if(i >= len) goto end;
235 //grab the date number. this is a UNIX date (seconds since 1970-1-1).
236 NSTimeInterval unixDate = strtod(ptr+i, &nextptr);
237 date = [NSDate dateWithTimeIntervalSince1970:unixDate];
239 /*we actually don't need any more information here. if we did, here's what we'd do...
244 if(i >= len) goto end;
247 NSRange range = { i, len - i };
248 [data replaceBytesInRange:NSMakeRange(0, i) withBytes:NULL length:0];
249 NSString *username = [NSString stringWithData:data encoding:NSUTF8StringEncoding];
258 * @brief Returns the date of the most recent Adium build (contacts adiumx.com asynchronously)
260 - (void)_requestVersionDict
262 [[NSURL URLWithString:VERSION_PLIST_URL] loadResourceDataNotifyingClient:self usingCache:NO];
266 * @brief Invoked when the versionDict was downloaded succesfully
268 * In response, we parse the received version information.
270 - (void)URLResourceDidFinishLoading:(NSURL *)sender
272 NSData *data = [sender resourceDataUsingCache:YES];
275 NSDictionary *versionDict;
277 versionDict = [NSPropertyListSerialization propertyListFromData:data
278 mutabilityOption:NSPropertyListImmutable
280 errorDescription:nil];
282 [self _versionReceived:versionDict];
287 * @brief Invoked when the versionDict could not be loaded
289 - (void)URLResourceDidCancelLoading:(NSURL *)sender
291 [self _versionReceived:nil];