Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

#76 Support for Plug-ins

  • Loading branch information...
commit 5e053395f81d30591b9b708192a7bef9915e68be 1 parent dedd632
catlan authored
View
2  NSApplication+AppCopies.m
@@ -18,7 +18,7 @@ - (int)copiesRunning
while ((currentApp = [appEnumerator nextObject]))
{
// Potential gotcha: the new version of your app better have the same NSApplicationName.
- if([[currentApp objectForKey:@"NSApplicationName"] isEqualToString:SUHostAppName()])
+ if([[currentApp objectForKey:@"NSApplicationName"] isEqualToString:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]])
count++;
}
return count;
View
2  NSFileManager+Verification.h
@@ -11,5 +11,5 @@
// For the paranoid folks!
@interface NSFileManager (SUVerification)
- (BOOL)validatePath:(NSString *)path withMD5Hash:(NSString *)hash;
-- (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature;
+- (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature withPublicDSAKey:(NSString *)pkeyString;
@end
View
6 NSFileManager+Verification.m
@@ -124,12 +124,10 @@ - (BOOL)validatePath:(NSString *)path withMD5Hash:(NSString *)hash
return [hash isEqualToString:[NSString stringWithCString:hexDigest]];
}
-- (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature
+- (BOOL)validatePath:(NSString *)path withEncodedDSASignature:(NSString *)encodedSignature withPublicDSAKey:(NSString *)pkeyString
{
BOOL result = NO;
- if(!encodedSignature) { return NO; }
-
- NSString *pkeyString = SUInfoValueForKey(SUPublicDSAKeyKey); // Fetch the app's public DSA key.
+ if (!encodedSignature) { return NO; }
if (!pkeyString) { return NO; }
// Remove whitespace around each line of the key.
View
5 SUAppcast.h
@@ -8,12 +8,15 @@
#import <Cocoa/Cocoa.h>
-@class RSS, SUAppcastItem;
+@class RSS, SUAppcastItem, SUUtilities;
@interface SUAppcast : NSObject {
NSArray *items;
id delegate;
+ SUUtilities *utilities;
}
+- (id)initWithUtilities:(SUUtilities *)aUtility;
+
- (void)fetchAppcastFromURL:(NSURL *)url;
- (void)setDelegate:delegate;
View
12 SUAppcast.m
@@ -13,6 +13,15 @@
@implementation SUAppcast
+- (id)initWithUtilities:(SUUtilities *)aUtility
+{
+ self = [super init];
+ if (self != nil) {
+ utilities = [aUtility retain];
+ }
+ return self;
+}
+
- (void)fetchAppcastFromURL:(NSURL *)url
{
[NSThread detachNewThreadSelector:@selector(_fetchAppcastFromURL:) toTarget:self withObject:url]; // let's not block the main thread
@@ -25,6 +34,7 @@ - (void)setDelegate:del
- (void)dealloc
{
+ [utilities release];
[items release];
[super dealloc];
}
@@ -46,7 +56,7 @@ - (void)_fetchAppcastFromURL:(NSURL *)url
RSS *feed;
@try
{
- NSString *userAgent = [NSString stringWithFormat: @"%@/%@ (Mac OS X) Sparkle/1.1", SUHostAppName(), SUHostAppVersion()];
+ NSString *userAgent = [NSString stringWithFormat: @"%@/%@ (Mac OS X) Sparkle/1.1", [utilities hostAppName], [utilities hostAppVersion]];
feed = [[RSS alloc] initWithURL:url normalize:YES userAgent:userAgent];
if (!feed)
View
5 SUAutomaticUpdateAlert.h
@@ -8,12 +8,13 @@
#import <Cocoa/Cocoa.h>
-@class SUAppcastItem;
+@class SUAppcastItem, SUUtilities;
@interface SUAutomaticUpdateAlert : NSWindowController {
SUAppcastItem *updateItem;
+ SUUtilities *utilities;
}
-- initWithAppcastItem:(SUAppcastItem *)item;
+- initWithAppcastItem:(SUAppcastItem *)item andUtilities:(SUUtilities *)aUtility;;
- (IBAction)relaunchNow:sender;
- (IBAction)relaunchLater:sender;
View
17 SUAutomaticUpdateAlert.m
@@ -12,7 +12,7 @@
@implementation SUAutomaticUpdateAlert
-- initWithAppcastItem:(SUAppcastItem *)item
+- initWithAppcastItem:(SUAppcastItem *)item andUtilities:(SUUtilities *)aUtility;
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"SUAutomaticUpdateAlert" ofType:@"nib"];
if (!path) // slight hack to resolve issues with running with in configurations
@@ -26,11 +26,20 @@ @implementation SUAutomaticUpdateAlert
[super initWithWindowNibPath:path owner:self];
updateItem = [item retain];
+ utilities = [aUtility retain];
[self setShouldCascadeWindows:NO];
return self;
}
+- (void) dealloc
+{
+ [utilities release];
+ [updateItem release];
+ [super dealloc];
+}
+
+
- (IBAction)relaunchNow:sender
{
[self close];
@@ -45,17 +54,17 @@ - (IBAction)relaunchLater:sender
- (NSImage *)applicationIcon
{
- return [NSImage imageNamed:@"NSApplicationIcon"];
+ return [utilities hostAppIcon];
}
- (NSString *)titleText
{
- return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ has been installed!", nil), SUHostAppDisplayName()];
+ return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ has been installed!", nil), [utilities hostAppDisplayName]];
}
- (NSString *)descriptionText
{
- return [NSString stringWithFormat:SULocalizedString(@"%@ %@ has been installed and will be ready to use next time %@ starts! Would you like to relaunch now?", nil), SUHostAppDisplayName(), [updateItem versionString], SUHostAppDisplayName()];
+ return [NSString stringWithFormat:SULocalizedString(@"%@ %@ has been installed and will be ready to use next time %@ starts! Would you like to relaunch now?", nil), [utilities hostAppDisplayName], [updateItem versionString], [utilities hostAppDisplayName]];
}
@end
View
37 SUBundleDefaults.h
@@ -0,0 +1,37 @@
+//
+// SUBundleDefaults.h
+// Sparkle
+//
+// Created by Christopher Atlan on 07.11.07.
+// Copyright 2007 Christopher Atlan. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@class SUUtilities;
+@interface SUBundleDefaults : NSObject {
+ SUUtilities *utilities;
+ NSString *applicationID;
+}
+
+- (id)initWithUtilitie:(SUUtilities *)theUtilities;
+
+- (id)objectForKey:(NSString *)defaultName;
+- (void)setObject:(id)value forKey:(NSString *)defaultName;
+- (void)removeObjectForKey:(NSString *)defaultName;
+
+- (NSString *)stringForKey:(NSString *)defaultName;
+- (NSArray *)arrayForKey:(NSString *)defaultName;
+- (NSDictionary *)dictionaryForKey:(NSString *)defaultName;
+- (NSData *)dataForKey:(NSString *)defaultName;
+- (NSArray *)stringArrayForKey:(NSString *)defaultName;
+/*
+- (int)integerForKey:(NSString *)defaultName;
+- (float)floatForKey:(NSString *)defaultName;
+- (BOOL)boolForKey:(NSString *)defaultName;
+
+- (void)setInteger:(int)value forKey:(NSString *)defaultName;
+- (void)setFloat:(float)value forKey:(NSString *)defaultName;
+- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
+*/
+@end
View
82 SUBundleDefaults.m
@@ -0,0 +1,82 @@
+//
+// SUBundleDefaults.m
+// Sparkle
+//
+// Created by Christopher Atlan on 07.11.07.
+// Copyright 2007 __MyCompanyName__. All rights reserved.
+//
+
+#import "SUBundleDefaults.h"
+#import "SUUtilities.h"
+
+@implementation SUBundleDefaults
+
+- (id)initWithUtilitie:(SUUtilities *)theUtilities
+{
+ self = [super init];
+ if (self != nil) {
+ utilities = [theUtilities retain];
+ applicationID = [utilities hostAppID];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [utilities release];
+ [applicationID release];
+ [super dealloc];
+}
+
+
+- (id)objectForKey:(NSString *)defaultName
+{
+ return (id)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (void)setObject:(id)value forKey:(NSString *)defaultName;
+{
+ CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ CFPreferencesSynchronize((CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (void)removeObjectForKey:(NSString *)defaultName
+{
+ CFPreferencesSetValue((CFStringRef)defaultName, NULL, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ CFPreferencesSynchronize((CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (NSString *)stringForKey:(NSString *)defaultName
+{
+ return (NSString *)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (NSArray *)arrayForKey:(NSString *)defaultName
+{
+ return (NSArray *)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (NSDictionary *)dictionaryForKey:(NSString *)defaultName
+{
+ return (NSDictionary *)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (NSData *)dataForKey:(NSString *)defaultName
+{
+ return (NSData *)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+
+- (NSArray *)stringArrayForKey:(NSString *)defaultName
+{
+ return (NSArray *)CFPreferencesCopyValue((CFStringRef)defaultName, (CFStringRef)applicationID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+}
+/*
+- (int)integerForKey:(NSString *)defaultName;
+- (float)floatForKey:(NSString *)defaultName;
+- (BOOL)boolForKey:(NSString *)defaultName;
+
+- (void)setInteger:(int)value forKey:(NSString *)defaultName;
+- (void)setFloat:(float)value forKey:(NSString *)defaultName;
+- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
+*/
+@end
View
5 SUStatusController.h
@@ -8,13 +8,16 @@
#import <Cocoa/Cocoa.h>
-
+@class SUUtilities;
@interface SUStatusController : NSWindowController {
+ SUUtilities *utilities;
double progressValue, maxProgressValue;
NSString *title, *statusText, *buttonTitle;
IBOutlet NSButton *actionButton;
}
+- initWithUtilities:(SUUtilities *)aUtility;
+
// Pass 0 for the max progress value to get an indeterminate progress bar.
// Pass nil for the status text to not show it.
- (void)beginActionWithTitle:(NSString *)title maxProgressValue:(double)maxProgressValue statusText:(NSString *)statusText;
View
8 SUStatusController.m
@@ -11,7 +11,7 @@
@implementation SUStatusController
-- init
+- initWithUtilities:(SUUtilities *)aUtility
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"SUStatus" ofType:@"nib"];
if (!path) // slight hack to resolve issues with running in debug configurations
@@ -23,11 +23,13 @@ @implementation SUStatusController
}
[super initWithWindowNibPath:path owner:self];
[self setShouldCascadeWindows:NO];
+ utilities = [aUtility retain];
return self;
}
- (void)dealloc
{
+ [utilities release];
[title release];
[statusText release];
[buttonTitle release];
@@ -42,12 +44,12 @@ - (void)awakeFromNib
- (NSString *)windowTitle
{
- return [NSString stringWithFormat:SULocalizedString(@"Updating %@", nil), SUHostAppDisplayName()];
+ return [NSString stringWithFormat:SULocalizedString(@"Updating %@", nil), [utilities hostAppDisplayName]];
}
- (NSImage *)applicationIcon
{
- return [NSImage imageNamed:@"NSApplicationIcon"];
+ return [utilities hostAppIcon];
}
- (void)beginActionWithTitle:(NSString *)aTitle maxProgressValue:(double)aMaxProgressValue statusText:(NSString *)aStatusText
View
5 SUUpdateAlert.h
@@ -15,9 +15,10 @@ typedef enum
SUSkipThisVersionChoice
} SUUpdateAlertChoice;
-@class WebView, SUAppcastItem;
+@class WebView, SUAppcastItem, SUUtilities;
@interface SUUpdateAlert : NSWindowController {
SUAppcastItem *updateItem;
+ SUUtilities *utilities;
id delegate;
IBOutlet WebView *releaseNotesView;
@@ -26,7 +27,7 @@ typedef enum
BOOL webViewFinishedLoading;
}
-- initWithAppcastItem:(SUAppcastItem *)item;
+- initWithAppcastItem:(SUAppcastItem *)item andUtilities:(SUUtilities *)aUtility;
- (void)setDelegate:delegate;
- (IBAction)installUpdate:sender;
View
20 SUUpdateAlert.m
@@ -13,7 +13,7 @@
@implementation SUUpdateAlert
-- initWithAppcastItem:(SUAppcastItem *)item
+- initWithAppcastItem:(SUAppcastItem *)item andUtilities:(SUUtilities *)aUtility
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"SUUpdateAlert" ofType:@"nib"];
if (!path) // slight hack to resolve issues with running with in configurations
@@ -27,6 +27,7 @@ @implementation SUUpdateAlert
[super initWithWindowNibPath:path owner:self];
updateItem = [item retain];
+ utilities = [aUtility retain];
[self setShouldCascadeWindows:NO];
return self;
@@ -35,6 +36,7 @@ @implementation SUUpdateAlert
- (void)dealloc
{
[updateItem release];
+ [utilities release];
[super dealloc];
}
@@ -89,15 +91,15 @@ - (void)displayReleaseNotes
- (BOOL)showsReleaseNotes
{
- if (!SUInfoValueForKey(SUShowReleaseNotesKey)) { return YES; } // defaults to YES
- return [SUInfoValueForKey(SUShowReleaseNotesKey) boolValue];
+ if (![utilities infoValueForKey:SUShowReleaseNotesKey]) { return YES; } // defaults to YES
+ return [[utilities infoValueForKey:SUShowReleaseNotesKey] boolValue];
}
- (BOOL)allowsAutomaticUpdates
{
- if (!SUInfoValueForKey(SUExpectsDSASignatureKey)) { return NO; } // automatic updating requires DSA-signed updates
- if (!SUInfoValueForKey(SUAllowsAutomaticUpdatesKey)) { return YES; } // defaults to YES
- return [SUInfoValueForKey(SUAllowsAutomaticUpdatesKey) boolValue];
+ if ([utilities infoValueForKey:SUExpectsDSASignatureKey]) { return NO; } // automatic updating requires DSA-signed updates
+ if ([utilities infoValueForKey:SUAllowsAutomaticUpdatesKey]) { return YES; } // defaults to YES
+ return [[utilities infoValueForKey:SUAllowsAutomaticUpdatesKey] boolValue];
}
- (void)awakeFromNib
@@ -143,17 +145,17 @@ - (BOOL)windowShouldClose:note
- (NSImage *)applicationIcon
{
- return [NSImage imageNamed:@"NSApplicationIcon"];
+ return [utilities hostAppIcon];
}
- (NSString *)titleText
{
- return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), SUHostAppDisplayName()];
+ return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [utilities hostAppDisplayName]];
}
- (NSString *)descriptionText
{
- return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available (you have %@). Would you like to download it now?", nil), SUHostAppDisplayName(), [updateItem versionString], SUHostAppVersionString()];
+ return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available (you have %@). Would you like to download it now?", nil), [utilities hostAppDisplayName], [updateItem versionString], [utilities hostAppVersionString]];
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:frame
View
9 SUUpdater.h
@@ -18,7 +18,7 @@
// getting, which it assumes are in the description (or body) field of the relevant RSS item.
// Set SUShowReleaseNotes to <false/> in Info.plist to hide the button.
-@class SUAppcastItem, SUUpdateAlert, SUStatusController;
+@class SUAppcastItem, SUUpdateAlert, SUStatusController, SUUtilities;
@interface SUUpdater : NSObject {
SUAppcastItem *updateItem;
@@ -35,8 +35,15 @@
BOOL updateInProgress;
NSString *currentSystemVersion;
+
+ NSBundle *updateBundle;
+ SUUtilities *utilities;
}
+- (id)initWithBundle:(NSBundle *)bundle;
+
+- (NSBundle *)updateBundle;
+
// This IBAction is meant for a main menu item. Hook up any menu item to this action,
// and Sparkle will check for updates and report back its findings verbosely.
- (IBAction)checkForUpdates:sender;
View
126 SUUpdater.m
@@ -39,7 +39,11 @@ @implementation SUUpdater
- init
{
- [super init];
+ self = [super init];
+
+ updateBundle = [[NSBundle mainBundle] retain];
+ utilities = [[SUUtilities alloc] initWithUpdater:self];
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:@"NSApplicationDidFinishLaunchingNotification" object:NSApp];
// OS version (Apple recommends using SystemVersion.plist instead of Gestalt() here, don't ask me why).
@@ -48,9 +52,35 @@ @implementation SUUpdater
NSString *versionPlistPath = @"/System/Library/CoreServices/SystemVersion.plist";
//gets a version string of the form X.Y.Z
currentSystemVersion = [[[NSDictionary dictionaryWithContentsOfFile:versionPlistPath] objectForKey:@"ProductVersion"] retain];
+
return self;
}
+
+- (id)initWithBundle:(NSBundle *)bundle
+{
+ self = [super init];
+ if (self != nil) {
+ updateBundle = [bundle retain];
+ utilities = [[SUUtilities alloc] initWithUpdater:self];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:@"NSApplicationDidFinishLaunchingNotification" object:NSApp];
+
+ // OS version (Apple recommends using SystemVersion.plist instead of Gestalt() here, don't ask me why).
+ // This code *should* use NSSearchPathForDirectoriesInDomains(NSCoreServiceDirectory, NSSystemDomainMask, YES)
+ // but that returns /Library/CoreServices for some reason
+ NSString *versionPlistPath = @"/System/Library/CoreServices/SystemVersion.plist";
+ //gets a version string of the form X.Y.Z
+ currentSystemVersion = [[[NSDictionary dictionaryWithContentsOfFile:versionPlistPath] objectForKey:@"ProductVersion"] retain];
+ }
+ return self;
+}
+
+- (NSBundle *)updateBundle
+{
+ return [[updateBundle retain] autorelease];
+}
+
- (void)scheduleCheckWithInterval:(NSTimeInterval)interval
{
if (checkTimer)
@@ -76,7 +106,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)note
if ([self storedCheckInterval])
{
NSTimeInterval interval = [self storedCheckInterval];
- NSDate *lastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:SULastCheckTimeKey];
+ NSDate *lastCheck = [[utilities standardBundleDefaults] objectForKey:SULastCheckTimeKey];
if (!lastCheck) { lastCheck = [NSDate date]; }
NSTimeInterval intervalSinceCheck = [[NSDate date] timeIntervalSinceDate:lastCheck];
if (intervalSinceCheck < interval)
@@ -94,20 +124,28 @@ - (void)applicationDidFinishLaunching:(NSNotification *)note
else
{
// There's no scheduled check, so let's see if we're supposed to check on startup.
- NSNumber *shouldCheckAtStartup = [[NSUserDefaults standardUserDefaults] objectForKey:SUCheckAtStartupKey];
+ NSNumber *shouldCheckAtStartup = [[utilities standardBundleDefaults] objectForKey:SUCheckAtStartupKey];
if (!shouldCheckAtStartup) // hasn't been set yet; ask the user
{
// Let's see if there's a key in Info.plist for a default, though. We'll let that override the dialog if it's there.
- NSNumber *infoStartupValue = SUInfoValueForKey(SUCheckAtStartupKey);
+ NSNumber *infoStartupValue = [utilities infoValueForKey:SUCheckAtStartupKey];
if (infoStartupValue)
{
shouldCheckAtStartup = infoStartupValue;
}
else
{
- shouldCheckAtStartup = [NSNumber numberWithBool:NSRunAlertPanel(SULocalizedString(@"Check for updates on startup?", nil), [NSString stringWithFormat:SULocalizedString(@"Would you like %@ to check for updates on startup? If not, you can initiate the check manually from the %@ menu.", nil), SUHostAppDisplayName(), SUHostAppDisplayName()], SULocalizedString(@"Yes", nil), SULocalizedString(@"No", nil), nil) == NSAlertDefaultReturn];
+ NSImage *bundleIcon = [utilities hostAppIcon];
+ NSImage *appIcon = [NSImage imageNamed: @"NSApplicationIcon"];
+ if ([appIcon setName:@"NSApplicationIconWorkAround"])
+ [bundleIcon setName:@"NSApplicationIcon"];
+
+ shouldCheckAtStartup = [NSNumber numberWithBool:NSRunAlertPanel(SULocalizedString(@"Check for updates on startup?", nil), [NSString stringWithFormat:SULocalizedString(@"Would you like %@ to check for updates on startup? If not, you can initiate the check manually from the %@ menu.", nil), [utilities hostAppDisplayName], [utilities hostAppDisplayName]], SULocalizedString(@"Yes", nil), SULocalizedString(@"No", nil), nil) == NSAlertDefaultReturn];
+
+ if ([bundleIcon setName:@"NSApplicationIconWorkAround2"])
+ [appIcon setName:@"NSApplicationIcon"];
}
- [[NSUserDefaults standardUserDefaults] setObject:shouldCheckAtStartup forKey:SUCheckAtStartupKey];
+ [[utilities standardBundleDefaults] setObject:shouldCheckAtStartup forKey:SUCheckAtStartupKey];
}
if ([shouldCheckAtStartup boolValue])
@@ -130,6 +168,12 @@ - (void)dealloc
if (currentSystemVersion)
[currentSystemVersion release];
+ if (updateBundle)
+ [updateBundle release];
+
+ if (utilities)
+ [utilities release];
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@@ -166,9 +210,9 @@ - (void)checkForUpdatesAndNotify:(BOOL)verbosity
updateInProgress = YES;
// A value in the user defaults overrides one in the Info.plist (so preferences panels can be created wherein users choose between beta / release feeds).
- NSString *appcastString = [[NSUserDefaults standardUserDefaults] objectForKey:SUFeedURLKey];
+ NSString *appcastString = [[utilities standardBundleDefaults] objectForKey:SUFeedURLKey];
if (!appcastString)
- appcastString = SUInfoValueForKey(SUFeedURLKey);
+ appcastString = [utilities infoValueForKey:SUFeedURLKey];
if (!appcastString) { [NSException raise:@"SUNoFeedURL" format:@"No feed URL is specified in the Info.plist or the user defaults!"]; }
SUAppcast *appcast = [[SUAppcast alloc] init];
@@ -179,15 +223,15 @@ - (void)checkForUpdatesAndNotify:(BOOL)verbosity
- (BOOL)automaticallyUpdates
{
// If the SUAllowsAutomaticUpdatesKey exists and is set to NO, return NO.
- if ([SUInfoValueForKey(SUAllowsAutomaticUpdatesKey) boolValue] == NO && SUInfoValueForKey(SUAllowsAutomaticUpdatesKey)) { return NO; }
+ if ([[utilities infoValueForKey:SUAllowsAutomaticUpdatesKey] boolValue] == NO && [utilities infoValueForKey:SUAllowsAutomaticUpdatesKey]) { return NO; }
// If we're not using DSA signatures, we aren't going to trust any updates automatically.
- if (![SUInfoValueForKey(SUExpectsDSASignatureKey) boolValue]) { return NO; }
+ if (![[utilities infoValueForKey:SUExpectsDSASignatureKey] boolValue]) { return NO; }
// If there's no setting, we default to NO.
- if (![[NSUserDefaults standardUserDefaults] objectForKey:SUAutomaticallyUpdateKey]) { return NO; }
+ if (![[utilities standardBundleDefaults] objectForKey:SUAutomaticallyUpdateKey]) { return NO; }
- return [[[NSUserDefaults standardUserDefaults] objectForKey:SUAutomaticallyUpdateKey] boolValue];
+ return [[[utilities standardBundleDefaults] objectForKey:SUAutomaticallyUpdateKey] boolValue];
}
- (BOOL)isAutomaticallyUpdating
@@ -198,7 +242,16 @@ - (BOOL)isAutomaticallyUpdating
- (void)showUpdateErrorAlertWithInfo:(NSString *)info
{
if ([self isAutomaticallyUpdating]) { return; }
+
+ NSImage *bundleIcon = [utilities hostAppIcon];
+ NSImage *appIcon = [NSImage imageNamed: @"NSApplicationIcon"];
+ if ([appIcon setName:@"NSApplicationIconWorkAround"])
+ [bundleIcon setName:@"NSApplicationIcon"];
+
NSRunAlertPanel(SULocalizedString(@"Update Error!", nil), info, SULocalizedString(@"Cancel", nil), nil, nil);
+
+ if ([bundleIcon setName:@"NSApplicationIconWorkAround2"])
+ [appIcon setName:@"NSApplicationIcon"];
}
- (NSTimeInterval)storedCheckInterval
@@ -212,13 +265,13 @@ - (NSTimeInterval)storedCheckInterval
// Returns the scheduled check interval stored in the user defaults / info.plist. User defaults override Info.plist.
long interval = 0; // 0 signifies not to do timed checking.
- if ([[NSUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey])
+ if ([[utilities standardBundleDefaults] objectForKey:SUScheduledCheckIntervalKey])
{
- interval = [[[NSUserDefaults standardUserDefaults] objectForKey:SUScheduledCheckIntervalKey] longValue];
+ interval = [[[utilities standardBundleDefaults] objectForKey:SUScheduledCheckIntervalKey] longValue];
}
- else if (SUInfoValueForKey(SUScheduledCheckIntervalKey))
+ else if ([utilities infoValueForKey:SUScheduledCheckIntervalKey])
{
- interval = [SUInfoValueForKey(SUScheduledCheckIntervalKey) longValue];
+ interval = [[utilities infoValueForKey:SUScheduledCheckIntervalKey] longValue];
}
if (interval >= MIN_INTERVAL)
return interval;
@@ -242,7 +295,7 @@ - (void)beginDownload
- (void)remindMeLater
{
// Clear out the skipped version so the dialog will actually come back if it was already skipped.
- [[NSUserDefaults standardUserDefaults] setObject:nil forKey:SUSkippedVersionKey];
+ [[utilities standardBundleDefaults] setObject:nil forKey:SUSkippedVersionKey];
if (checkInterval)
[self scheduleCheckWithInterval:checkInterval];
@@ -261,7 +314,7 @@ - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoi
{
case SUInstallUpdateChoice:
// Clear out the skipped version so the dialog will come back if the download fails.
- [[NSUserDefaults standardUserDefaults] setObject:nil forKey:SUSkippedVersionKey];
+ [[utilities standardBundleDefaults] setObject:nil forKey:SUSkippedVersionKey];
[self beginDownload];
break;
@@ -272,7 +325,7 @@ - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoi
case SUSkipThisVersionChoice:
updateInProgress = NO;
- [[NSUserDefaults standardUserDefaults] setObject:[updateItem fileVersion] forKey:SUSkippedVersionKey];
+ [[utilities standardBundleDefaults] setObject:[updateItem fileVersion] forKey:SUSkippedVersionKey];
if (checkInterval)
[self scheduleCheckWithInterval:checkInterval];
break;
@@ -281,7 +334,7 @@ - (void)updateAlert:(SUUpdateAlert *)alert finishedWithChoice:(SUUpdateAlertChoi
- (void)showUpdatePanel
{
- updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem];
+ updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem andUtilities:utilities];
[updateAlert setDelegate:self];
// Only show the update alert if the app is active; otherwise, we'll wait until it is.
@@ -303,7 +356,7 @@ - (void)appcastDidFailToLoad:(SUAppcast *)ac
- (BOOL)newVersionAvailable
{
BOOL canRunOnCurrentSystem = (SUStandardVersionComparison([updateItem minimumSystemVersion], [self systemVersionString]) != NSOrderedAscending);
- return (canRunOnCurrentSystem && (SUStandardVersionComparison([updateItem fileVersion], SUHostAppVersion()) == NSOrderedAscending));
+ return (canRunOnCurrentSystem && (SUStandardVersionComparison([updateItem fileVersion], [utilities hostAppVersion]) == NSOrderedAscending));
// Want straight-up string comparison like Sparkle 1.0b3 and earlier? Uncomment the line below and comment the one above.
// return ![SUHostAppVersion() isEqualToString:[updateItem fileVersion]];
}
@@ -323,14 +376,14 @@ - (void)appcastDidFinishLoading:(SUAppcast *)ac
[ac autorelease];
// Record the time of the check for host app use and for interval checks on startup.
- [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:SULastCheckTimeKey];
+ [[utilities standardBundleDefaults] setObject:[NSDate date] forKey:SULastCheckTimeKey];
if (![updateItem fileVersion])
{
[NSException raise:@"SUAppcastException" format:@"Can't extract a version string from the appcast feed. The filenames should look like YourApp_1.5.tgz, where 1.5 is the version number."];
}
- if (!verbose && [[[NSUserDefaults standardUserDefaults] objectForKey:SUSkippedVersionKey] isEqualToString:[updateItem fileVersion]]) { updateInProgress = NO; return; }
+ if (!verbose && [[[utilities standardBundleDefaults] objectForKey:SUSkippedVersionKey] isEqualToString:[updateItem fileVersion]]) { updateInProgress = NO; return; }
if ([self newVersionAvailable])
{
@@ -353,7 +406,15 @@ - (void)appcastDidFinishLoading:(SUAppcast *)ac
{
if (verbose) // We only notify on no new version when we're being verbose.
{
- NSRunAlertPanel(SULocalizedString(@"You're up to date!", nil), [NSString stringWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), SUHostAppDisplayName(), SUHostAppVersionString()], SULocalizedString(@"OK", nil), nil, nil);
+ NSImage *bundleIcon = [utilities hostAppIcon];
+ NSImage *appIcon = [NSImage imageNamed: @"NSApplicationIcon"];
+ if ([appIcon setName:@"NSApplicationIconWorkAround"])
+ [bundleIcon setName:@"NSApplicationIcon"];
+
+ NSRunAlertPanel(SULocalizedString(@"You're up to date!", nil), [NSString stringWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [utilities hostAppDisplayName], [utilities hostAppVersionString]], SULocalizedString(@"OK", nil), nil, nil);
+
+ if ([bundleIcon setName:@"NSApplicationIconWorkAround2"])
+ [appIcon setName:@"NSApplicationIcon"];
}
updateInProgress = NO;
}
@@ -447,10 +508,11 @@ - (void)extractUpdate
}
// DSA verification, if activated by the developer
- if ([SUInfoValueForKey(SUExpectsDSASignatureKey) boolValue])
+ if ([[utilities infoValueForKey:SUExpectsDSASignatureKey] boolValue])
{
NSString *dsaSignature = [updateItem DSASignature];
- if (![[NSFileManager defaultManager] validatePath:downloadPath withEncodedDSASignature:dsaSignature])
+ NSString *pkeyString = [utilities infoValueForKey:SUPublicDSAKeyKey]; // Fetch the app's public DSA key.
+ if (![[NSFileManager defaultManager] validatePath:downloadPath withEncodedDSASignature:dsaSignature withPublicDSAKey:pkeyString])
{
[NSException raise:@"SUUnarchiveException" format:@"DSA verification of the update archive failed."];
}
@@ -495,7 +557,7 @@ - (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
- (IBAction)installAndRestart:sender
{
NSString *currentAppPath = [[NSBundle mainBundle] bundlePath];
- NSString *newAppDownloadPath = [[downloadPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[SUUnlocalizedInfoValueForKey(@"CFBundleName") stringByAppendingPathExtension:@"app"]];
+ NSString *newAppDownloadPath = [[downloadPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[[utilities unlocalizedInfoValueForKey:@"CFBundleName"] stringByAppendingPathExtension:@"app"]];
@try
{
if (![self isAutomaticallyUpdating])
@@ -511,10 +573,10 @@ - (IBAction)installAndRestart:sender
// We assume that the archive will contain a file named {CFBundleName}.app
// (where, obviously, CFBundleName comes from Info.plist)
- if (!SUUnlocalizedInfoValueForKey(@"CFBundleName")) { [NSException raise:@"SUInstallException" format:@"This application has no CFBundleName! This key must be set to the application's name."]; }
+ if (![utilities unlocalizedInfoValueForKey:@"CFBundleName"]) { [NSException raise:@"SUInstallException" format:@"This application has no CFBundleName! This key must be set to the application's name."]; }
// Search subdirectories for the application
- NSString *file, *appName = [SUUnlocalizedInfoValueForKey(@"CFBundleName") stringByAppendingPathExtension:@"app"];
+ NSString *file, *appName = [[utilities unlocalizedInfoValueForKey:@"CFBundleName"] stringByAppendingPathExtension:@"app"];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:[downloadPath stringByDeletingLastPathComponent]];
while ((file = [dirEnum nextObject]))
{
@@ -532,7 +594,7 @@ - (IBAction)installAndRestart:sender
if (!newAppDownloadPath || ![[NSFileManager defaultManager] fileExistsAtPath:newAppDownloadPath])
{
- [NSException raise:@"SUInstallException" format:@"The update archive didn't contain an application with the proper name: %@. Remember, the updated app's file name must be identical to {CFBundleString}.app", [SUUnlocalizedInfoValueForKey(@"CFBundleName") stringByAppendingPathExtension:@"app"]];
+ [NSException raise:@"SUInstallException" format:@"The update archive didn't contain an application with the proper name: %@. Remember, the updated app's file name must be identical to {CFBundleString}.app", [[utilities unlocalizedInfoValueForKey:@"CFBundleName"] stringByAppendingPathExtension:@"app"]];
}
}
@catch(NSException *e)
@@ -559,7 +621,7 @@ - (IBAction)installAndRestart:sender
// Outside of the @try block because we want to be a little more informative on this error.
if (![[NSFileManager defaultManager] movePathWithAuthentication:newAppDownloadPath toPath:currentAppPath])
{
- [self showUpdateErrorAlertWithInfo:[NSString stringWithFormat:SULocalizedString(@"%@ does not have permission to write to the application's directory! Are you running off a disk image? If not, ask your system administrator for help.", nil), SUHostAppDisplayName()]];
+ [self showUpdateErrorAlertWithInfo:[NSString stringWithFormat:SULocalizedString(@"%@ does not have permission to write to the application's directory! Are you running off a disk image? If not, ask your system administrator for help.", nil), [utilities hostAppDisplayName]]];
[self abandonUpdate];
return;
}
@@ -568,7 +630,7 @@ - (IBAction)installAndRestart:sender
// Prompt for permission to restart if we're automatically updating.
if ([self isAutomaticallyUpdating])
{
- SUAutomaticUpdateAlert *alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:updateItem];
+ SUAutomaticUpdateAlert *alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:updateItem andUtilities:utilities];
if ([NSApp runModalForWindow:[alert window]] == NSAlertAlternateReturn)
{
[alert release];
View
25 SUUtilities.h
@@ -8,12 +8,25 @@
#import <Cocoa/Cocoa.h>
-id SUInfoValueForKey(NSString *key);
-id SUUnlocalizedInfoValueForKey(NSString *key);
-NSString *SUHostAppName();
-NSString *SUHostAppDisplayName();
-NSString *SUHostAppVersion();
-NSString *SUHostAppVersionString();
+@class SUUpdater, SUBundleDefaults;
+@interface SUUtilities : NSObject {
+ SUUpdater *updater;
+ SUBundleDefaults *defaults;
+}
+
++ (NSString *)localizedStringForKey:(NSString *)key withComment:(NSString *)comment;
+
+- (id)initWithUpdater:(SUUpdater *)aUpdater;
+- (id)unlocalizedInfoValueForKey:(NSString *)key;
+- (id)infoValueForKey:(NSString *)key;
+- (NSString *)hostAppName;
+- (NSString *)hostAppDisplayName;
+- (NSString *)hostAppVersion;
+- (NSString *)hostAppVersionString;
+- (NSString *)hostAppID;
+- (NSImage *)hostAppIcon;
+- (SUBundleDefaults *)standardBundleDefaults;
+@end
NSComparisonResult SUStandardVersionComparison(NSString * versionA, NSString * versionB);
View
109 SUUtilities.m
@@ -7,51 +7,111 @@
//
#import "SUUtilities.h"
+#import "SUUpdater.h"
+#import "SUBundleDefaults.h"
-@interface SUUtilities : NSObject
- +(NSString *)localizedStringForKey:(NSString *)key withComment:(NSString *)comment;
-@end
-id SUUnlocalizedInfoValueForKey(NSString *key)
+@implementation SUUtilities
+
++ (NSString *)localizedStringForKey:(NSString *)key withComment:(NSString *)comment
+{
+ return NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleForClass:[self class]], comment);
+}
+
+- (id)initWithUpdater:(SUUpdater *)aUpdater
{
- return [[[NSBundle mainBundle] infoDictionary] objectForKey:key];
+ self = [super init];
+ if (self != nil) {
+ updater = [aUpdater retain];
+ defaults = [[SUBundleDefaults alloc] initWithUtilitie:self];
+ }
+ return self;
}
-id SUInfoValueForKey(NSString *key)
+- (void) dealloc
{
- return [[NSBundle mainBundle] objectForInfoDictionaryKey:key];
+ [updater release];
+ [defaults release];
+
+ [super dealloc];
}
-NSString *SUHostAppName()
+- (id)unlocalizedInfoValueForKey:(NSString *)key
{
- if (SUInfoValueForKey(@"CFBundleName")) { return SUInfoValueForKey(@"CFBundleName"); }
- return [[[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]] stringByDeletingPathExtension];
+ return [[[updater updateBundle] infoDictionary] objectForKey:key];
}
-NSString *SUHostAppDisplayName()
+- (id)infoValueForKey:(NSString *)key
{
- if (SUInfoValueForKey(@"CFBundleDisplayName")) { return SUInfoValueForKey(@"CFBundleDisplayName"); }
- return SUHostAppName();
+ return [[updater updateBundle] objectForInfoDictionaryKey:key];
}
-NSString *SUHostAppVersion()
+- (NSString *)hostAppName
{
- return SUInfoValueForKey(@"CFBundleVersion");
+ if ([self infoValueForKey:@"CFBundleName"]) { return [self infoValueForKey:@"CFBundleName"]; }
+ return [[[NSFileManager defaultManager] displayNameAtPath:[[updater updateBundle] bundlePath]] stringByDeletingPathExtension];
}
-NSString *SUHostAppVersionString()
+- (NSString *)hostAppDisplayName
{
- NSString *shortVersionString = SUInfoValueForKey(@"CFBundleShortVersionString");
+ if ([self infoValueForKey:@"CFBundleDisplayName"]) { return [self infoValueForKey:@"CFBundleDisplayName"]; }
+ return [self hostAppName];
+}
+
+- (NSString *)hostAppVersion
+{
+ return [self infoValueForKey:@"CFBundleVersion"];
+}
+
+- (NSString *)hostAppVersionString
+{
+ NSString *shortVersionString = [self infoValueForKey:@"CFBundleShortVersionString"];
if (shortVersionString)
{
- if (![shortVersionString isEqualToString:SUHostAppVersion()])
- shortVersionString = [shortVersionString stringByAppendingFormat:@"/%@", SUHostAppVersion()];
+ if (![shortVersionString isEqualToString:[self hostAppVersion]])
+ shortVersionString = [shortVersionString stringByAppendingFormat:@"/%@", [self hostAppVersion]];
return shortVersionString;
}
else
- return SUHostAppVersion(); // fall back on CFBundleVersion
+ return [self hostAppVersion]; // fall back on CFBundleVersion
}
+- (NSString *)hostAppID
+{
+ return [self unlocalizedInfoValueForKey:@"CFBundleIdentifier"];
+}
+
+- (NSImage *)hostAppIcon
+{
+ // draw the app's icon
+ NSImage* iconImage = nil;
+ NSString* iconFileStr = [[[updater updateBundle] infoDictionary] objectForKey:@"CFBundleIconFile"];
+ if (iconFileStr != nil && [iconFileStr length] > 0)
+ iconFileStr = [[updater updateBundle] pathForResource:iconFileStr ofType:@"icns"];
+ if (iconFileStr != nil && [iconFileStr length] > 0)
+
+ {
+ // we have a real icon
+ iconImage = [[NSImage alloc] initWithContentsOfFile:iconFileStr];
+ }
+ else
+ {
+ // no particular app icon defined, use the default system icon
+ iconImage = [NSImage imageNamed: @"NSApplicationIcon"];
+ // or
+ //NSString* appIconType = NSFileTypeForHFSTypeCode(kGenericApplicationIcon);
+ //iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:appIconType];
+ }
+ return iconImage;
+}
+
+- (SUBundleDefaults *)standardBundleDefaults
+{
+ return [[defaults retain] autorelease];
+}
+
+@end
+
NSString *SULocalizedString(NSString *key, NSString *comment) {
return [SUUtilities localizedStringForKey:key withComment:comment];
}
@@ -192,12 +252,3 @@ NSComparisonResult SUStandardVersionComparison(NSString *versionA, NSString *ver
// The 2 strings are identical
return NSOrderedSame;
}
-
-@implementation SUUtilities
-
-+ (NSString *)localizedStringForKey:(NSString *)key withComment:(NSString *)comment
-{
- return NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleForClass:[self class]], comment);
-}
-
-@end
View
8 Sparkle.xcodeproj/project.pbxproj
@@ -56,6 +56,8 @@
61B5FCDF09C52A9F00B25A18 /* SUUpdateAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = 61B5FCA009C5228F00B25A18 /* SUUpdateAlert.h */; settings = {ATTRIBUTES = (Public, ); }; };
61BBDF820A49220C00378739 /* Sparkle.icns in Resources */ = {isa = PBXBuildFile; fileRef = 61BBDF810A49220C00378739 /* Sparkle.icns */; };
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; };
+ D18A64E80CE2588900C49C71 /* SUBundleDefaults.h in Headers */ = {isa = PBXBuildFile; fileRef = D18A64E60CE2588900C49C71 /* SUBundleDefaults.h */; };
+ D18A64E90CE2588900C49C71 /* SUBundleDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = D18A64E70CE2588900C49C71 /* SUBundleDefaults.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -227,6 +229,8 @@
61BBDF810A49220C00378739 /* Sparkle.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Sparkle.icns; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D18A64E60CE2588900C49C71 /* SUBundleDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBundleDefaults.h; sourceTree = "<group>"; };
+ D18A64E70CE2588900C49C71 /* SUBundleDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBundleDefaults.m; sourceTree = "<group>"; };
D2F7E79907B2D74100F64583 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
DA142A740CC79FEA00CE54F3 /* relaunch */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = relaunch; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -393,6 +397,8 @@
61B5F8F309C4CE5900B25A18 /* Utilities */ = {
isa = PBXGroup;
children = (
+ D18A64E60CE2588900C49C71 /* SUBundleDefaults.h */,
+ D18A64E70CE2588900C49C71 /* SUBundleDefaults.m */,
61B5F8DD09C4CE3C00B25A18 /* md5.c */,
61B5F8DE09C4CE3C00B25A18 /* md5.h */,
61B5F8DF09C4CE3C00B25A18 /* NSString+extras.h */,
@@ -444,6 +450,7 @@
61299B3609CB04E000B7442F /* Sparkle.h in Headers */,
6120721209CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h in Headers */,
345AF9E40A5D707200D7DA6F /* SUStatusChecker.h in Headers */,
+ D18A64E80CE2588900C49C71 /* SUBundleDefaults.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -637,6 +644,7 @@
61299A8E09CA790200B7442F /* SUUnarchiver.m in Sources */,
6120721309CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m in Sources */,
345AF9E50A5D707200D7DA6F /* SUStatusChecker.m in Sources */,
+ D18A64E90CE2588900C49C71 /* SUBundleDefaults.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Please sign in to comment.
Something went wrong with that request. Please try again.