cog/Frameworks/Sparkle/SUHost.m

271 lines
9.4 KiB
Objective-C

//
// SUHost.m
// Sparkle
//
// Copyright 2008 Andy Matuschak. All rights reserved.
//
#import "SUHost.h"
#import "SUConstants.h"
#import "SUSystemProfiler.h"
#import <sys/mount.h> // For statfs for isRunningOnReadOnlyVolume
#import "SULog.h"
@implementation SUHost
- (id)initWithBundle:(NSBundle *)aBundle
{
if ((self = [super init]))
{
if (aBundle == nil) aBundle = [NSBundle mainBundle];
bundle = [aBundle retain];
if (![bundle bundleIdentifier])
SULog(@"Sparkle Error: the bundle being updated at %@ has no CFBundleIdentifier! This will cause preference read/write to not work properly.", bundle);
defaultsDomain = [[bundle objectForInfoDictionaryKey:SUDefaultsDomainKey] retain];
if (!defaultsDomain)
defaultsDomain = [[bundle bundleIdentifier] retain];
// If we're using the main bundle's defaults we'll use the standard user defaults mechanism, otherwise we have to get CF-y.
usesStandardUserDefaults = [defaultsDomain isEqualToString:[[NSBundle mainBundle] bundleIdentifier]];
}
return self;
}
- (void)dealloc
{
[defaultsDomain release];
[bundle release];
[super dealloc];
}
- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@, %@>", [self class], [self bundlePath], [self installationPath]]; }
- (NSBundle *)bundle
{
return bundle;
}
- (NSString *)bundlePath
{
return [bundle bundlePath];
}
- (NSString *)appSupportPath
{
NSArray *appSupportPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportPath = nil;
if (!appSupportPaths || [appSupportPaths count] == 0)
{
SULog(@"Failed to find app support directory! Using ~/Library/Application Support...");
appSupportPath = [@"~/Library/Application Support" stringByExpandingTildeInPath];
}
else
appSupportPath = [appSupportPaths objectAtIndex:0];
appSupportPath = [appSupportPath stringByAppendingPathComponent:[self name]];
return appSupportPath;
}
- (NSString *)installationPath
{
#if NORMALIZE_INSTALLED_APP_NAME
// We'll install to "#{CFBundleName}.app", but only if that path doesn't already exist. If we're "Foo 4.2.app," and there's a "Foo.app" in this directory, we don't want to overwrite it! But if there's no "Foo.app," we'll take that name.
NSString *normalizedAppPath = [[[bundle bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent: [NSString stringWithFormat: @"%@.%@", [bundle objectForInfoDictionaryKey:@"CFBundleName"], [[bundle bundlePath] pathExtension]]];
if (![[NSFileManager defaultManager] fileExistsAtPath:[[[bundle bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent: [NSString stringWithFormat: @"%@.%@", [bundle objectForInfoDictionaryKey:@"CFBundleName"], [[bundle bundlePath] pathExtension]]]])
return normalizedAppPath;
#endif
return [bundle bundlePath];
}
- (NSString *)name
{
NSString *name = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name) return name;
name = [self objectForInfoDictionaryKey:@"CFBundleName"];
if (name) return name;
return [[[NSFileManager defaultManager] displayNameAtPath:[bundle bundlePath]] stringByDeletingPathExtension];
}
- (NSString *)version
{
NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
if (!version || [version isEqualToString:@""])
[NSException raise:@"SUNoVersionException" format:@"This host (%@) has no CFBundleVersion! This attribute is required.", [self bundlePath]];
return version;
}
- (NSString *)displayVersion
{
NSString *shortVersionString = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
if (shortVersionString)
return shortVersionString;
else
return [self version]; // Fall back on the normal version string.
}
- (NSImage *)icon
{
// Cache the application icon.
NSString *iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType:@"icns"];
// According to the OS X docs, "CFBundleIconFile - This key identifies the file containing
// the icon for the bundle. The filename you specify does not need to include the .icns
// extension, although it may."
//
// However, if it *does* include the '.icns' the above method fails (tested on OS X 10.3.9) so we'll also try:
if (!iconPath)
iconPath = [bundle pathForResource:[bundle objectForInfoDictionaryKey:@"CFBundleIconFile"] ofType: nil];
NSImage *icon = [[[NSImage alloc] initWithContentsOfFile:iconPath] autorelease];
// Use a default icon if none is defined.
if (!icon) {
BOOL isMainBundle = (bundle == [NSBundle mainBundle]);
// Starting with 10.6, iconForFileType: accepts a UTI.
NSString *fileType = nil;
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5)
fileType = isMainBundle ? NSFileTypeForHFSTypeCode(kGenericApplicationIcon) : @".bundle";
else
fileType = isMainBundle ? (NSString*)kUTTypeApplication : (NSString*)kUTTypeBundle;
icon = [[NSWorkspace sharedWorkspace] iconForFileType:fileType];
}
return icon;
}
- (BOOL)isRunningOnReadOnlyVolume
{
struct statfs statfs_info;
statfs([[bundle bundlePath] fileSystemRepresentation], &statfs_info);
return (statfs_info.f_flags & MNT_RDONLY);
}
- (BOOL)isBackgroundApplication
{
ProcessSerialNumber PSN;
GetCurrentProcess(&PSN);
CFDictionaryRef processInfo = ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
BOOL isElement = [[(NSDictionary *)processInfo objectForKey:@"LSUIElement"] boolValue];
if (processInfo)
CFRelease(processInfo);
return isElement;
}
- (NSString *)publicDSAKey
{
// Maybe the key is just a string in the Info.plist.
NSString *key = [bundle objectForInfoDictionaryKey:SUPublicDSAKeyKey];
if (key) { return key; }
// More likely, we've got a reference to a Resources file by filename:
NSString *keyFilename = [self objectForInfoDictionaryKey:SUPublicDSAKeyFileKey];
if (!keyFilename) { return nil; }
NSError *ignoreErr = nil;
return [NSString stringWithContentsOfFile:[bundle pathForResource:keyFilename ofType:nil] encoding:NSASCIIStringEncoding error: &ignoreErr];
}
- (NSArray *)systemProfile
{
return [[SUSystemProfiler sharedSystemProfiler] systemProfileArrayForHost:self];
}
- (id)objectForInfoDictionaryKey:(NSString *)key
{
return [bundle objectForInfoDictionaryKey:key];
}
- (BOOL)boolForInfoDictionaryKey:(NSString *)key
{
return [[self objectForInfoDictionaryKey:key] boolValue];
}
- (id)objectForUserDefaultsKey:(NSString *)defaultName
{
// Under Tiger, CFPreferencesCopyAppValue doesn't get values from NSRegistrationDomain, so anything
// passed into -[NSUserDefaults registerDefaults:] is ignored. The following line falls
// back to using NSUserDefaults, but only if the host bundle is the main bundle.
if (usesStandardUserDefaults)
return [[NSUserDefaults standardUserDefaults] objectForKey:defaultName];
CFPropertyListRef obj = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)defaultsDomain);
return [(id)CFMakeCollectable(obj) autorelease];
}
- (void)setObject:(id)value forUserDefaultsKey:(NSString *)defaultName;
{
if (usesStandardUserDefaults)
{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:defaultName];
}
else
{
CFPreferencesSetValue((CFStringRef)defaultName, value, (CFStringRef)defaultsDomain, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
CFPreferencesSynchronize((CFStringRef)defaultsDomain, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
}
}
- (BOOL)boolForUserDefaultsKey:(NSString *)defaultName
{
if (usesStandardUserDefaults)
return [[NSUserDefaults standardUserDefaults] boolForKey:defaultName];
BOOL value;
CFPropertyListRef plr = CFPreferencesCopyAppValue((CFStringRef)defaultName, (CFStringRef)defaultsDomain);
if (plr == NULL)
value = NO;
else
{
value = (BOOL)CFBooleanGetValue((CFBooleanRef)plr);
CFRelease(plr);
}
return value;
}
- (void)setBool:(BOOL)value forUserDefaultsKey:(NSString *)defaultName
{
if (usesStandardUserDefaults)
{
[[NSUserDefaults standardUserDefaults] setBool:value forKey:defaultName];
}
else
{
CFPreferencesSetValue((CFStringRef)defaultName, (CFBooleanRef)[NSNumber numberWithBool:value], (CFStringRef)defaultsDomain, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
CFPreferencesSynchronize((CFStringRef)defaultsDomain, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
}
}
- (id)objectForKey:(NSString *)key {
return [self objectForUserDefaultsKey:key] ? [self objectForUserDefaultsKey:key] : [self objectForInfoDictionaryKey:key];
}
- (BOOL)boolForKey:(NSString *)key {
return [self objectForUserDefaultsKey:key] ? [self boolForUserDefaultsKey:key] : [self boolForInfoDictionaryKey:key];
}
+ (NSString *)systemVersionString
{
// This returns a version string of the form X.Y.Z
// There may be a better way to deal with the problem that gestaltSystemVersionMajor
// et al. are not defined in 10.3, but this is probably good enough.
NSString* verStr = nil;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4
SInt32 major, minor, bugfix;
OSErr err1 = Gestalt(gestaltSystemVersionMajor, &major);
OSErr err2 = Gestalt(gestaltSystemVersionMinor, &minor);
OSErr err3 = Gestalt(gestaltSystemVersionBugFix, &bugfix);
if (!err1 && !err2 && !err3)
{
verStr = [NSString stringWithFormat:@"%ld.%ld.%ld", (long)major, (long)minor, (long)bugfix];
}
else
#endif
{
NSString *versionPlistPath = @"/System/Library/CoreServices/SystemVersion.plist";
verStr = [[NSDictionary dictionaryWithContentsOfFile:versionPlistPath] objectForKey:@"ProductVersion"];
}
return verStr;
}
@end