cog/Updates/MacPADSocket.m

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