From c95557a49b52aa6a26a5390e30e90364b17b38e1 Mon Sep 17 00:00:00 2001 From: Antonin Hildebrand Date: Sat, 3 Apr 2010 21:30:42 +0200 Subject: [PATCH] implemented string version comparison (using proven code from the Sparkle project by Andy Matuschak) This was needed for TotalFinder which wants to safely check Finder versions. Finder uses OS version in its Info.plist like this: CFBundleVersion 10.6.4 --- .gitignore | 4 + SIMBL.xcodeproj/project.pbxproj | 10 ++ src/SIMBL.m | 19 ++-- src/SUStandardVersionComparator.h | 36 +++++++ src/SUStandardVersionComparator.m | 161 ++++++++++++++++++++++++++++++ src/SUVersionComparisonProtocol.h | 27 +++++ 6 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 src/SUStandardVersionComparator.h create mode 100644 src/SUStandardVersionComparator.m create mode 100644 src/SUVersionComparisonProtocol.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..107e63d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +.DS_Store +rez/SIMBL.r +.*\.xcodeproj/.*\.(mode*|pbxuser) diff --git a/SIMBL.xcodeproj/project.pbxproj b/SIMBL.xcodeproj/project.pbxproj index 1f2f9bf..92fd2e5 100644 --- a/SIMBL.xcodeproj/project.pbxproj +++ b/SIMBL.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 276156DC1060C08C00FEF11C /* SIMBL.m in Sources */ = {isa = PBXBuildFile; fileRef = E0AF789609B3031900F50FE7 /* SIMBL.m */; }; 276156DF1060C09900FEF11C /* SIMBLPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = E0AF789909B3031900F50FE7 /* SIMBLPlugin.m */; }; 27D59F5610791EE100424BBD /* NSAlert_SIMBL.m in Sources */ = {isa = PBXBuildFile; fileRef = E0AF789409B3031900F50FE7 /* NSAlert_SIMBL.m */; }; + D6EC084C1167CD5E005804D7 /* SUStandardVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = D6EC083E1167CBA0005804D7 /* SUStandardVersionComparator.m */; }; + D6EC08591167CE30005804D7 /* SUStandardVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = D6EC083E1167CBA0005804D7 /* SUStandardVersionComparator.m */; }; E04B83EF104E73D80001E0E3 /* SIMBL.sdef in Rez */ = {isa = PBXBuildFile; fileRef = E04B83EE104E73D80001E0E3 /* SIMBL.sdef */; }; E0C2C2FE104F376500553034 /* NSAlert_SIMBL.m in Sources */ = {isa = PBXBuildFile; fileRef = E0AF789409B3031900F50FE7 /* NSAlert_SIMBL.m */; }; E0C2C2FF104F376600553034 /* SIMBL.m in Sources */ = {isa = PBXBuildFile; fileRef = E0AF789609B3031900F50FE7 /* SIMBL.m */; }; @@ -61,6 +63,9 @@ 276144D9105F523C00F906F6 /* SIMBLAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SIMBLAgent.m; sourceTree = ""; }; 27614521105F55D500F906F6 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = /System/Library/Frameworks/ScriptingBridge.framework; sourceTree = ""; }; 8D5B49B6048680CD000E48DA /* SIMBL.osax */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SIMBL.osax; sourceTree = BUILT_PRODUCTS_DIR; }; + D6EC083D1167CBA0005804D7 /* SUVersionComparisonProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUVersionComparisonProtocol.h; sourceTree = ""; }; + D6EC083E1167CBA0005804D7 /* SUStandardVersionComparator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUStandardVersionComparator.m; sourceTree = ""; }; + D6EC083F1167CBA0005804D7 /* SUStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUStandardVersionComparator.h; sourceTree = ""; }; E043030209B300D6000FD3C5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E04B83EE104E73D80001E0E3 /* SIMBL.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.sdef; path = SIMBL.sdef; sourceTree = ""; }; E0AF789209B3031900F50FE7 /* DTMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DTMacros.h; path = src/DTMacros.h; sourceTree = SOURCE_ROOT; }; @@ -162,6 +167,9 @@ E0AF789109B3031900F50FE7 /* src */ = { isa = PBXGroup; children = ( + D6EC083D1167CBA0005804D7 /* SUVersionComparisonProtocol.h */, + D6EC083E1167CBA0005804D7 /* SUStandardVersionComparator.m */, + D6EC083F1167CBA0005804D7 /* SUStandardVersionComparator.h */, E0AF789209B3031900F50FE7 /* DTMacros.h */, E0AF789309B3031900F50FE7 /* NSAlert_SIMBL.h */, E0AF789409B3031900F50FE7 /* NSAlert_SIMBL.m */, @@ -293,6 +301,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D6EC08591167CE30005804D7 /* SUStandardVersionComparator.m in Sources */, 276144F6105F531D00F906F6 /* SIMBLAgent.m in Sources */, 276156DC1060C08C00FEF11C /* SIMBL.m in Sources */, 276156DF1060C09900FEF11C /* SIMBLPlugin.m in Sources */, @@ -305,6 +314,7 @@ buildActionMask = 2147483647; files = ( E0C2C2FE104F376500553034 /* NSAlert_SIMBL.m in Sources */, + D6EC084C1167CD5E005804D7 /* SUStandardVersionComparator.m in Sources */, E0C2C2FF104F376600553034 /* SIMBL.m in Sources */, E0C2C300104F376700553034 /* SIMBLPlugin.m in Sources */, ); diff --git a/src/SIMBL.m b/src/SIMBL.m index 68f4d74..8340990 100644 --- a/src/SIMBL.m +++ b/src/SIMBL.m @@ -8,6 +8,7 @@ #import "SIMBL.h" #import "SIMBLPlugin.h" #import "NSAlert_SIMBL.h" +#import "SUStandardVersionComparator.h" #import @@ -231,18 +232,16 @@ + (BOOL) shouldApplication:(NSBundle*)_appBundle loadBundle:(SIMBLPlugin*)_bundl if (missingFramework) continue; - int appVersion = [[_appBundle _dt_bundleVersion] intValue]; + NSString* appVersion = [_appBundle _dt_bundleVersion]; - int minVersion = 0; - NSNumber* number; - if ((number = [targetAppProperties objectForKey:SIMBLMinBundleVersion])) - minVersion = [number intValue]; - - int maxVersion = 0; - if ((number = [targetAppProperties objectForKey:SIMBLMaxBundleVersion])) - maxVersion = [number intValue]; + NSString* minVersion = [targetAppProperties objectForKey:SIMBLMinBundleVersion]; + NSString* maxVersion = [targetAppProperties objectForKey:SIMBLMaxBundleVersion]; - if ((maxVersion && appVersion > maxVersion) || (minVersion && appVersion < minVersion)) + SUStandardVersionComparator* comparator = [SUStandardVersionComparator defaultComparator]; + + SIMBLLogDebug(@"checking version: app=%@ must be in range %@ - %@", appVersion, minVersion, maxVersion); + if ((maxVersion && [comparator compareVersion:appVersion toVersion:maxVersion]==NSOrderedDescending) || + (minVersion && [comparator compareVersion:appVersion toVersion:minVersion]==NSOrderedAscending)) { [NSAlert errorAlert:NSLocalizedStringFromTableInBundle(@"Error", SIMBLStringTable, DTOwnerBundle, @"Error alert primary message") withDetails:NSLocalizedStringFromTableInBundle(@"%@ %@ (v%@) has not been tested with the plugin %@ %@ (v%@). As a precaution, it has not been loaded. Please contact the plugin developer for further information.", SIMBLStringTable, DTOwnerBundle, @"Error alert details, substitute application and plugin version strings"), [_appBundle _dt_name], [_appBundle _dt_version], [_appBundle _dt_bundleVersion], [_bundle _dt_name], [_bundle _dt_version], [_bundle _dt_bundleVersion]]; continue; diff --git a/src/SUStandardVersionComparator.h b/src/SUStandardVersionComparator.h new file mode 100644 index 0000000..0bc1570 --- /dev/null +++ b/src/SUStandardVersionComparator.h @@ -0,0 +1,36 @@ +// +// SUStandardVersionComparator.h +// Sparkle +// +// Created by Andy Matuschak on 12/21/07. +// Copyright 2007 Andy Matuschak. All rights reserved. +// + +#ifndef SUSTANDARDVERSIONCOMPARATOR_H +#define SUSTANDARDVERSIONCOMPARATOR_H + + +#import "SUVersionComparisonProtocol.h" + +/*! + @class + @abstract Sparkle's default version comparator. + @discussion This comparator is adapted from MacPAD, by Kevin Ballard. It's "dumb" in that it does essentially string comparison, in components split by character type. +*/ +@interface SUStandardVersionComparator : NSObject { } + +/*! + @method + @abstract Returns a singleton instance of the comparator. +*/ ++ (SUStandardVersionComparator *)defaultComparator; + +/*! + @method + @abstract Compares version strings through textual analysis. + @discussion See the implementation for more details. +*/ +- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; +@end + +#endif diff --git a/src/SUStandardVersionComparator.m b/src/SUStandardVersionComparator.m new file mode 100644 index 0000000..3006be6 --- /dev/null +++ b/src/SUStandardVersionComparator.m @@ -0,0 +1,161 @@ +// +// SUStandardVersionComparator.m +// Sparkle +// +// Created by Andy Matuschak on 12/21/07. +// Copyright 2007 Andy Matuschak. All rights reserved. +// + +// #import "Sparkle.h" +#import "SUStandardVersionComparator.h" + +@implementation SUStandardVersionComparator + ++ (SUStandardVersionComparator *)defaultComparator +{ + static SUStandardVersionComparator *defaultComparator = nil; + if (defaultComparator == nil) + defaultComparator = [[SUStandardVersionComparator alloc] init]; + return defaultComparator; +} + +typedef enum { + kNumberType, + kStringType, + kPeriodType +} SUCharacterType; + +- (SUCharacterType)typeOfCharacter:(NSString *)character +{ + if ([character isEqualToString:@"."]) { + return kPeriodType; + } else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[character characterAtIndex:0]]) { + return kNumberType; + } else { + return kStringType; + } +} + +- (NSArray *)splitVersionString:(NSString *)version +{ + NSString *character; + NSMutableString *s; + NSUInteger i, n; + SUCharacterType oldType, newType; + NSMutableArray *parts = [NSMutableArray array]; + if ([version length] == 0) { + // Nothing to do here + return parts; + } + s = [[[version substringToIndex:1] mutableCopy] autorelease]; + oldType = [self typeOfCharacter:s]; + n = [version length] - 1; + for (i = 1; i <= n; ++i) { + character = [version substringWithRange:NSMakeRange(i, 1)]; + newType = [self typeOfCharacter:character]; + if (oldType != newType || oldType == kPeriodType) { + // We've reached a new segment + NSString *aPart = [[[NSString alloc] initWithString:s] autorelease]; + [parts addObject:aPart]; + [s setString:character]; + } else { + // Add character to string and continue + [s appendString:character]; + } + oldType = newType; + } + + // Add the last part onto the array + [parts addObject:[NSString stringWithString:s]]; + return parts; +} + +- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; +{ + NSArray *partsA = [self splitVersionString:versionA]; + NSArray *partsB = [self splitVersionString:versionB]; + + NSString *partA, *partB; + NSUInteger i, n; + int intA, intB; + SUCharacterType typeA, typeB; + + n = MIN([partsA count], [partsB count]); + for (i = 0; i < n; ++i) { + partA = [partsA objectAtIndex:i]; + partB = [partsB objectAtIndex:i]; + + typeA = [self typeOfCharacter:partA]; + typeB = [self typeOfCharacter:partB]; + + // Compare types + if (typeA == typeB) { + // Same type; we can compare + if (typeA == kNumberType) { + intA = [partA intValue]; + intB = [partB intValue]; + if (intA > intB) { + return NSOrderedDescending; + } else if (intA < intB) { + return NSOrderedAscending; + } + } 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 NSOrderedDescending; + } else if (typeA == kStringType && typeB != kStringType) { + // typeB wins + return NSOrderedAscending; + } else { + // One is a number and the other is a period. The period is invalid + if (typeA == kNumberType) { + return NSOrderedDescending; + } else { + return NSOrderedAscending; + } + } + } + } + // 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 index of the part we want. + NSString *missingPart; + SUCharacterType missingType; + NSComparisonResult shorterResult, largerResult; + + if ([partsA count] > [partsB count]) { + missingPart = [partsA objectAtIndex:n]; + shorterResult = NSOrderedAscending; + largerResult = NSOrderedDescending; + } else { + missingPart = [partsB objectAtIndex:n]; + shorterResult = NSOrderedDescending; + largerResult = NSOrderedAscending; + } + + missingType = [self typeOfCharacter: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; +} + + +@end diff --git a/src/SUVersionComparisonProtocol.h b/src/SUVersionComparisonProtocol.h new file mode 100644 index 0000000..3d11ae8 --- /dev/null +++ b/src/SUVersionComparisonProtocol.h @@ -0,0 +1,27 @@ +// +// SUVersionComparisonProtocol.h +// Sparkle +// +// Created by Andy Matuschak on 12/21/07. +// Copyright 2007 Andy Matuschak. All rights reserved. +// + +#ifndef SUVERSIONCOMPARISONPROTOCOL_H +#define SUVERSIONCOMPARISONPROTOCOL_H + +/*! + @protocol + @abstract Implement this protocol to provide version comparison facilities for Sparkle. +*/ +@protocol SUVersionComparison + +/*! + @method + @abstract An abstract method to compare two version strings. + @discussion Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, and NSOrderedSame if they are equivalent. +*/ +- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB; + +@end + +#endif