Skip to content

Commit

Permalink
implemented string version comparison (using proven code from the Spa…
Browse files Browse the repository at this point in the history
…rkle 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:
<key>CFBundleVersion</key>
<string>10.6.4</string>
  • Loading branch information
darwin committed Apr 3, 2010
1 parent 2e1cb8f commit c95557a
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 10 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
build
.DS_Store
rez/SIMBL.r
.*\.xcodeproj/.*\.(mode*|pbxuser)
10 changes: 10 additions & 0 deletions SIMBL.xcodeproj/project.pbxproj
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -61,6 +63,9 @@
276144D9105F523C00F906F6 /* SIMBLAgent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SIMBLAgent.m; sourceTree = "<group>"; };
27614521105F55D500F906F6 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = /System/Library/Frameworks/ScriptingBridge.framework; sourceTree = "<absolute>"; };
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 = "<group>"; };
D6EC083E1167CBA0005804D7 /* SUStandardVersionComparator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUStandardVersionComparator.m; sourceTree = "<group>"; };
D6EC083F1167CBA0005804D7 /* SUStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUStandardVersionComparator.h; sourceTree = "<group>"; };
E043030209B300D6000FD3C5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E04B83EE104E73D80001E0E3 /* SIMBL.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.sdef; path = SIMBL.sdef; sourceTree = "<group>"; };
E0AF789209B3031900F50FE7 /* DTMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DTMacros.h; path = src/DTMacros.h; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
);
Expand Down
19 changes: 9 additions & 10 deletions src/SIMBL.m
Expand Up @@ -8,6 +8,7 @@
#import "SIMBL.h"
#import "SIMBLPlugin.h"
#import "NSAlert_SIMBL.h"
#import "SUStandardVersionComparator.h"

#import <objc/objc-class.h>

Expand Down Expand Up @@ -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;
Expand Down
36 changes: 36 additions & 0 deletions 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 <SUVersionComparison> { }

/*!
@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
161 changes: 161 additions & 0 deletions 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
27 changes: 27 additions & 0 deletions 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

0 comments on commit c95557a

Please sign in to comment.