400 lines
13 KiB
Objective-C
400 lines
13 KiB
Objective-C
//
|
|
// MacPADSocket.m
|
|
// MacPAD Version Check
|
|
//
|
|
// Created by Kevin Ballard on Sun Dec 07 2003.
|
|
// Copyright (c) 2003-2004 TildeSoft. All rights reserved.
|
|
//
|
|
|
|
#import "MacPADSocket.h"
|
|
|
|
// Constant strings
|
|
NSString *MacPADErrorCode = @"MacPADErrorCode";
|
|
NSString *MacPADErrorMessage = @"MacPADErrorMessage";
|
|
NSString *MacPADNewVersionAvailable = @"MacPADNewVersionAvailable";
|
|
|
|
// NSNotifications
|
|
NSString *MacPADErrorOccurredNotification = @"MacPADErrorOccurredNotification";
|
|
NSString *MacPADCheckFinishedNotification = @"MacPADCheckFinishedNotification";
|
|
|
|
enum {
|
|
kNumberType,
|
|
kStringType,
|
|
kPeriodType
|
|
};
|
|
|
|
@implementation MacPADSocket
|
|
|
|
// Code
|
|
- (id)init
|
|
{
|
|
if (self = [super init]) {
|
|
_fileHandle = nil;
|
|
_fileURL = nil;
|
|
_currentVersion = nil;
|
|
_newVersion = nil;
|
|
_releaseNotes = nil;
|
|
_productPageURL = nil;
|
|
_productDownloadURLs = nil;
|
|
_buffer = nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)initiateCheck:(id)sender;
|
|
{
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
// DBLog(@"In thread...reading url");
|
|
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfURL:_fileURL];
|
|
// DBLog(@"URL read");
|
|
[self processDictionary:dict];
|
|
[pool release];
|
|
}
|
|
|
|
- (void)performCheck:(NSURL *)url withVersion:(NSString *)version
|
|
{
|
|
// Make sure we were actually *given* stuff
|
|
if (url == nil || version == nil) {
|
|
// Bah
|
|
[self returnError:kMacPADResultMissingValues message:@"URL or version was nil"];
|
|
return;
|
|
}
|
|
|
|
// Save the current version and URL
|
|
[_currentVersion release];
|
|
_currentVersion = [version copy];
|
|
[_fileURL release];
|
|
_fileURL = [url copy];
|
|
|
|
// DBLog(@"Detaching thread for updater");
|
|
[NSThread detachNewThreadSelector:@selector(initiateCheck:) toTarget:self withObject:nil];
|
|
}
|
|
|
|
- (void)performCheckWithVersion:(NSString *)version
|
|
{
|
|
// This method makes use of the MacPAD.url file inside the application bundle
|
|
// If this file isn't there, or it's not in the correct format, this will return
|
|
// error kMacPADResultMissingValues with an appropriate message
|
|
// If it is there, it calls performCheck:withVersion: with the URL
|
|
NSString *path = [[NSBundle mainBundle] pathForResource:@"MacPAD" ofType:@"url"];
|
|
if (path == nil) {
|
|
// File is missing
|
|
[self returnError:kMacPADResultMissingValues message:@"MacPAD.url file was not found"];
|
|
return;
|
|
}
|
|
NSString *contents = [NSString stringWithContentsOfFile:path];
|
|
if (contents == nil) {
|
|
// The file can't be opened
|
|
[self returnError:kMacPADResultMissingValues message:@"The MacPAD.url file can't be opened"];
|
|
return;
|
|
}
|
|
|
|
NSString *urlString;
|
|
NSRange range = [contents rangeOfString:@"URL="];
|
|
if (range.location != NSNotFound) {
|
|
// We have a URL= prefix
|
|
range.location += range.length;
|
|
range.length = [contents length] - range.location;
|
|
urlString = [contents substringWithRange:range];
|
|
} else {
|
|
// The file is the URL
|
|
urlString = contents;
|
|
}
|
|
// Strip whitespace
|
|
urlString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
// Perform the check
|
|
[self performCheck:[NSURL URLWithString:urlString] withVersion:version];
|
|
}
|
|
|
|
- (void)performCheckWithURL:(NSURL *)url
|
|
{
|
|
// Gets the version from the Info.plist file and calls performCheck:withVersion:
|
|
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
|
|
[self performCheck:url withVersion:version];
|
|
}
|
|
|
|
- (void)performCheck
|
|
{
|
|
// Gets the version from the Info.plist file and calls performCheckWithVersion:
|
|
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
|
|
[self performCheckWithVersion:version];
|
|
}
|
|
|
|
- (void)setDelegate:(id)delegate
|
|
{
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
if (_delegate != nil) {
|
|
// Unregister with the notification center
|
|
[nc removeObserver:_delegate name:MacPADErrorOccurredNotification object:self];
|
|
[nc removeObserver:_delegate name:MacPADCheckFinishedNotification object:self];
|
|
[_delegate autorelease];
|
|
}
|
|
_delegate = [delegate retain];
|
|
// Register the new MacPADSocketNotification methods for the delegate
|
|
// Only register if the delegate implements it, though
|
|
if ([_delegate respondsToSelector:@selector(macPADErrorOccurred:)]) {
|
|
[nc addObserver:_delegate selector:@selector(macPADErrorOccurred:)
|
|
name:MacPADErrorOccurredNotification object:self];
|
|
}
|
|
if ([_delegate respondsToSelector:@selector(macPADCheckFinished:)]) {
|
|
[nc addObserver:_delegate selector:@selector(macPADCheckFinished:)
|
|
name:MacPADCheckFinishedNotification object:self];
|
|
}
|
|
}
|
|
|
|
- (NSString *)releaseNotes
|
|
{
|
|
if (_releaseNotes == nil) {
|
|
return @"";
|
|
} else {
|
|
return [[_releaseNotes copy] autorelease];
|
|
}
|
|
}
|
|
|
|
- (NSString *)newVersion
|
|
{
|
|
if (_newVersion == nil) {
|
|
return @"";
|
|
} else {
|
|
return [[_newVersion copy] autorelease];
|
|
}
|
|
}
|
|
|
|
- (NSString *)productPageURL
|
|
{
|
|
if (_productPageURL == nil) {
|
|
return @"";
|
|
} else {
|
|
return [[_productPageURL copy] autorelease];
|
|
}
|
|
}
|
|
|
|
- (NSString *)productDownloadURL
|
|
{
|
|
if (_productDownloadURLs != nil && [_productDownloadURLs count] >= 1) {
|
|
return [_productDownloadURLs objectAtIndex:0];
|
|
} else {
|
|
return @"";
|
|
}
|
|
}
|
|
|
|
- (NSArray *)productDownloadURLs
|
|
{
|
|
if (_productDownloadURLs == nil) {
|
|
return [NSArray array];
|
|
} else {
|
|
return [[_productDownloadURLs copy] autorelease];
|
|
}
|
|
}
|
|
|
|
- (void)returnError:(MacPADResultCode)code message:(NSString *)msg
|
|
{
|
|
NSNumber *yesno = [NSNumber numberWithBool:(code == kMacPADResultNewVersion)];
|
|
NSNumber *errorCode = [NSNumber numberWithInt:code];
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:yesno, MacPADNewVersionAvailable,
|
|
msg, MacPADErrorMessage, errorCode, MacPADErrorCode, nil];
|
|
if (code == 0 || code == 5) {
|
|
// Not an error
|
|
[self performSelectorOnMainThread:@selector(returnSuccess:) withObject:userInfo waitUntilDone:NO];
|
|
} else {
|
|
// It's an error
|
|
[self performSelectorOnMainThread:@selector(returnFailure:) withObject:userInfo waitUntilDone:NO];
|
|
}
|
|
}
|
|
|
|
- (void)returnSuccess:(NSDictionary *)userInfo
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MacPADCheckFinishedNotification
|
|
object:self userInfo:userInfo];
|
|
}
|
|
|
|
- (void)returnFailure:(NSDictionary *)userInfo
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MacPADErrorOccurredNotification
|
|
object:self userInfo:userInfo];
|
|
}
|
|
|
|
- (void)processDictionary:(NSDictionary *)dict
|
|
{
|
|
if (dict == nil) {
|
|
DBLog(@"Update error: dict is nil");
|
|
[self returnError:kMacPADResultInvalidURL message:@"Remote file or URL was invalid"];
|
|
return;
|
|
}
|
|
// DBLog(@"HOLY MOLY");
|
|
_newVersion = [[dict objectForKey:@"productVersion"] copy];
|
|
if (_newVersion == nil) {
|
|
// File is missing version information
|
|
[self returnError:kMacPADResultBadSyntax message:@"Product version information missing"];
|
|
return;
|
|
}
|
|
|
|
// Get release notes
|
|
_releaseNotes = [[dict objectForKey:@"productReleaseNotes"] copy];
|
|
|
|
// Get product page URL
|
|
_productPageURL = [[dict objectForKey:@"productPageURL"] copy];
|
|
|
|
// Get the first product download URL
|
|
_productDownloadURLs = [[dict objectForKey:@"productDownloadURL"] copy];
|
|
|
|
// Compare versions
|
|
if ([self compareVersion:_newVersion toVersion:_currentVersion] == NSOrderedAscending) {
|
|
// It's a new version
|
|
[self returnError:kMacPADResultNewVersion message:@"New version available"];
|
|
} else {
|
|
[self returnError:kMacPADResultNoNewVersion message:@"No new version available"];
|
|
}
|
|
|
|
// We're done
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
// Unregister the delegate with the notification center
|
|
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
[nc removeObserver:_delegate name:MacPADErrorOccurredNotification object:self];
|
|
[nc removeObserver:_delegate name:MacPADCheckFinishedNotification object:self];
|
|
[nc removeObserver:self];
|
|
|
|
// Release objects
|
|
[_delegate release];
|
|
[_fileHandle release];
|
|
[_currentVersion release];
|
|
[_buffer release];
|
|
[_newVersion release];
|
|
[_releaseNotes release];
|
|
[_productPageURL release];
|
|
[_productDownloadURLs release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB
|
|
{
|
|
NSArray *partsA = [self splitVersion:versionA];
|
|
NSArray *partsB = [self splitVersion:versionB];
|
|
|
|
NSString *partA, *partB;
|
|
int i, n, typeA, typeB, intA, intB;
|
|
|
|
n = MIN([partsA count], [partsB count]);
|
|
for (i = 0; i < n; ++i) {
|
|
partA = [partsA objectAtIndex:i];
|
|
partB = [partsB objectAtIndex:i];
|
|
|
|
typeA = [self getCharType:partA];
|
|
typeB = [self getCharType:partB];
|
|
|
|
// Compare types
|
|
if (typeA == typeB) {
|
|
// Same type; we can compare
|
|
if (typeA == kNumberType) {
|
|
intA = [partA intValue];
|
|
intB = [partB intValue];
|
|
if (intA > intB) {
|
|
return NSOrderedAscending;
|
|
} else if (intA < intB) {
|
|
return NSOrderedDescending;
|
|
}
|
|
} else if (typeA == kStringType) {
|
|
NSComparisonResult result = [partA compare:partB];
|
|
if (result != NSOrderedSame) {
|
|
return result;
|
|
}
|
|
}
|
|
} else {
|
|
// Not the same type? Now we have to do some validity checking
|
|
if (typeA != kStringType && typeB == kStringType) {
|
|
// typeA wins
|
|
return NSOrderedAscending;
|
|
} else if (typeA == kStringType && typeB != kStringType) {
|
|
// typeB wins
|
|
return NSOrderedDescending;
|
|
} else {
|
|
// One is a number and the other is a period. The period is invalid
|
|
if (typeA == kNumberType) {
|
|
return NSOrderedAscending;
|
|
} else {
|
|
return NSOrderedDescending;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// The versions are equal up to the point where they both still have parts
|
|
// Lets check to see if one is larger than the other
|
|
if ([partsA count] != [partsB count]) {
|
|
// Yep. Lets get the next part of the larger
|
|
// n holds the value we want
|
|
NSString *missingPart;
|
|
int missingType, shorterResult, largerResult;
|
|
|
|
if ([partsA count] > [partsB count]) {
|
|
missingPart = [partsA objectAtIndex:n];
|
|
shorterResult = NSOrderedDescending;
|
|
largerResult = NSOrderedAscending;
|
|
} else {
|
|
missingPart = [partsB objectAtIndex:n];
|
|
shorterResult = NSOrderedAscending;
|
|
largerResult = NSOrderedDescending;
|
|
}
|
|
|
|
missingType = [self getCharType:missingPart];
|
|
// Check the type
|
|
if (missingType == kStringType) {
|
|
// It's a string. Shorter version wins
|
|
return shorterResult;
|
|
} else {
|
|
// It's a number/period. Larger version wins
|
|
return largerResult;
|
|
}
|
|
}
|
|
|
|
// The 2 strings are identical
|
|
return NSOrderedSame;
|
|
}
|
|
|
|
- (NSArray *)splitVersion:(NSString *)version
|
|
{
|
|
NSString *character;
|
|
NSMutableString *s;
|
|
int i, n, oldType, newType;
|
|
NSMutableArray *parts = [NSMutableArray array];
|
|
if ([version length] == 0) {
|
|
// Nothing to do here
|
|
return parts;
|
|
}
|
|
s = [[[version substringToIndex:1] mutableCopy] autorelease];
|
|
oldType = [self getCharType:s];
|
|
n = [version length] - 1;
|
|
for (i = 1; i <= n; ++i) {
|
|
character = [version substringWithRange:NSMakeRange(i, 1)];
|
|
newType = [self getCharType:character];
|
|
if (oldType != newType || oldType == kPeriodType) {
|
|
// We've reached a new segment
|
|
[parts addObject:[s copy]];
|
|
[s setString:character];
|
|
} else {
|
|
// Add character to string and continue
|
|
[s appendString:character];
|
|
}
|
|
oldType = newType;
|
|
}
|
|
|
|
// Add the last part onto the array
|
|
[parts addObject:[s copy]];
|
|
return parts;
|
|
}
|
|
|
|
- (int)getCharType:(NSString *)character
|
|
{
|
|
if ([character isEqualToString:@"."]) {
|
|
return kPeriodType;
|
|
} else if ([character isEqualToString:@"0"] || [character intValue] != 0) {
|
|
return kNumberType;
|
|
} else {
|
|
return kStringType;
|
|
}
|
|
}
|
|
@end
|