Merged [15040]: Trying some magic: 5 seconds after the last unreachable host is repor...
[adiumx.git] / Source / CPFVersionChecker.m
bloba4ede2e728cc9c3fc17f80dde714a5744c5ac4ef
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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;
39 @end
41 /*!
42  * @class CPFVersionChecker
43  * @brief Checks for new releases of Adium
44  *
45  * The version checker checks for new releases of Adium and notifies the user when one is available.
46  *
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.
51  */
52 @implementation CPFVersionChecker
54 /*!
55  * @brief Install the version checker
56  */
57 - (void)installPlugin
59         //Configure our default preferences
60         [[adium preferenceController] registerDefaults:[NSDictionary dictionaryNamed:VERSION_CHECKER_DEFAULTS 
61                                                                                                                                                 forClass:[self class]]
62                                                                                   forGroup:PREF_GROUP_UPDATING];
63         
64         //Menu item for checking manually
65     versionCheckerMenuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:VERSION_CHECKER_TITLE 
66                                                                                                                                                                    target:self 
67                                                                                                                                                                    action:@selector(manualCheckForNewVersion:)
68                                                                                                                                                         keyEquivalent:@""] autorelease];
69     [[adium menuController] addMenuItem:versionCheckerMenuItem toLocation:LOC_Adium_About];
70         
71         //Observe connectivity changes
72         [[NSNotificationCenter defaultCenter] addObserver:self
73                                                                                          selector:@selector(networkConnectivityChanged:)
74                                                                                                  name:AINetwork_ConnectivityChanged
75                                                                                            object:nil];
76         
77         //Check for an update now
78         [self automaticCheckForNewVersion:nil];
79         
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))
82                                                                                           target:self
83                                                                                         selector:@selector(automaticCheckForNewVersion:)
84                                                                                         userInfo:nil
85                                                                                          repeats:YES] retain];
88 /*!
89  * @brief Uninstall the version checker
90  */
91 - (void)uninstallPlugin
93         [timer invalidate];
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.
105  */
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.
118  */
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];
130                 }else{
131                         //If the network is not available, check when it becomes available
132                         checkWhenConnectionBecomesAvailable = YES;
133                 }
134         }
138  * @brief When a network connection becomes available, check for an update if we haven't already
139  */
140 - (void)networkConnectivityChanged:(NSNotification *)notification
142         if(checkWhenConnectionBecomesAvailable && [[notification object] intValue]){
143                 [self automaticCheckForNewVersion:nil];
144         }
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
153  */
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]];
162         }
163                 
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];
168         
169         //If the user has already been informed of this update previously, don't bother them
170         if(checkingManually || !lastDateDisplayedToUser || (![lastDateDisplayedToUser isEqualToDate:newestDate])){
171                 if(!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];
177                 }else{
178                         //Display 'update' message always
179                         [ESVersionCheckerWindowController showUpdateWindowFromBuild:thisDate toBuild:newestDate];
180                         
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];
184                 }
185         }
186         
187         //Beta Expiration (Designed to be annoying)
188         //Beta expiration checking is performed in addition to regular version checking
189         if(BETA_RELEASE){
190                 NSString        *betaNumber = [versionDict objectForKey:VERSION_BETA_PLIST_KEY];
191                 NSDate          *betaDate = nil;
192                 
193                 if(versionDict && number) betaDate = [NSDate dateWithTimeIntervalSince1970:[betaNumber doubleValue]];
194                 if(!betaDate){
195                         [ESVersionCheckerWindowController showCannotConnectWindow];
196                 }else if(![thisDate isEqualToDate:betaDate] && ![thisDate isEqualToDate:[thisDate laterDate:betaDate]]){
197                         [ESVersionCheckerWindowController showUpdateWindowFromBuild:thisDate toBuild:betaDate];
198                 }
199         }
203 //Build Dates ----------------------------------------------------------------------------------------------------------
204 #pragma mark Build Dates
206  * @brief Returns the date of this build
207  */
208 - (NSDate *)dateOfThisBuild
210         NSDate *date = nil;
212         NSString *path = [[NSBundle mainBundle] pathForResource:@"buildnum" ofType:nil];
213         NSMutableData *data = [NSMutableData dataWithContentsOfFile:path];
214         if(data) {
215                 [data increaseLengthBy:1]; //nul-terminates.
217                 const char *ptr = [data bytes];
218                 char *nextptr;
219                 unsigned len    = [data length];
220                 unsigned i      = 0;
222                 //first character: 'r'. skip it.
223                 ++i;
224                 if(i >= len) goto end;
226                 //grab the build number.
227                 unsigned long buildnum = strtoul(ptr+i, &nextptr, 10);
228 #               pragma unused(buildnum)
229                 i = nextptr - ptr;
231                 //skip the '|' (with a space on each side of it).
232                 i += 3;
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...
240                 i = nextptr - ptr;
242                 //skip the '|'.
243                 i += 3;
244                 if(i >= len) goto end;
246                 //grab the author.
247                 NSRange range = { i, len - i };
248                 [data replaceBytesInRange:NSMakeRange(0, i) withBytes:NULL length:0];
249                 NSString *username = [NSString stringWithData:data encoding:NSUTF8StringEncoding];
250                  */
251         }
253 end:
254         return date;
258  * @brief Returns the date of the most recent Adium build (contacts adiumx.com asynchronously)
259  */
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.
269  */
270 - (void)URLResourceDidFinishLoading:(NSURL *)sender
272         NSData                  *data = [sender resourceDataUsingCache:YES];
273         
274         if(data){
275                 NSDictionary    *versionDict;
277                 versionDict = [NSPropertyListSerialization propertyListFromData:data
278                                                                                                            mutabilityOption:NSPropertyListImmutable
279                                                                                                                                  format:nil
280                                                                                                            errorDescription:nil];
281                 
282                 [self _versionReceived:versionDict];
283         }
287  * @brief Invoked when the versionDict could not be loaded
288  */
289 - (void)URLResourceDidCancelLoading:(NSURL *)sender
291         [self _versionReceived:nil];
293 @end